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.
- package/API_STABILITY.md +271 -0
- package/CHANGELOG.md +330 -1
- package/README.md +18 -4
- package/SECURITY.md +209 -0
- package/dist/adapter-kit/contracts.d.ts +87 -0
- package/dist/adapter-kit/contracts.js +237 -0
- package/dist/adapter-kit/errors.d.ts +39 -0
- package/dist/adapter-kit/errors.js +52 -0
- package/dist/adapter-kit/index.d.ts +144 -0
- package/dist/adapter-kit/index.js +178 -0
- package/dist/adapter-kit/semver.d.ts +12 -0
- package/dist/adapter-kit/semver.js +55 -0
- package/dist/agent/middleware/pii.js +2 -33
- package/dist/context-hub/hub.d.ts +53 -0
- package/dist/context-hub/hub.js +108 -3
- package/dist/context-hub/index.d.ts +2 -1
- package/dist/context-hub/index.js +2 -1
- package/dist/context-hub/signing.d.ts +95 -0
- package/dist/context-hub/signing.js +88 -0
- package/dist/core/secure-stream.js +4 -3
- package/dist/doctor/index.d.ts +4 -2
- package/dist/doctor/index.js +29 -0
- package/dist/embeddings/adapters.js +2 -1
- package/dist/evals/judge.js +2 -5
- package/dist/evaluate/cache.d.ts +0 -14
- package/dist/evaluate/cache.js +0 -30
- package/dist/evaluate/file-cache.d.ts +15 -0
- package/dist/evaluate/file-cache.js +30 -0
- package/dist/evaluate/index.d.ts +2 -1
- package/dist/evaluate/index.js +2 -1
- package/dist/evaluate/review.js +2 -2
- package/dist/evaluate/run.js +2 -2
- package/dist/evaluate/threads.d.ts +187 -0
- package/dist/evaluate/threads.js +187 -0
- package/dist/evaluate/version.js +3 -3
- package/dist/graph/graph.js +2 -2
- package/dist/graph/node-cache.js +3 -3
- package/dist/hallucination/rag.d.ts +4 -1
- package/dist/hallucination/rag.js +6 -1
- package/dist/images/asset-stores.js +2 -3
- package/dist/images/asset-support.js +2 -2
- package/dist/images/assets.js +1 -2
- package/dist/images/evals.js +4 -4
- package/dist/insights/proposals.js +1 -2
- package/dist/insights/regressions.d.ts +26 -2
- package/dist/insights/regressions.js +129 -3
- package/dist/loaders/git.js +8 -1
- package/dist/loaders/web.js +7 -1
- package/dist/mcp/client.d.ts +14 -0
- package/dist/mcp/client.js +59 -1
- package/dist/mcp/registry.d.ts +6 -0
- package/dist/mcp/registry.js +7 -0
- package/dist/operations/adapters.d.ts +2 -1
- package/dist/operations/runner.js +2 -0
- package/dist/operations/stats.js +11 -4
- package/dist/postgres/index.d.ts +2 -1
- package/dist/postgres/index.js +4 -0
- package/dist/postgres/operations.js +11 -4
- package/dist/postgres/store.d.ts +7 -0
- package/dist/postgres/store.js +21 -0
- package/dist/postgres/tenancy.d.ts +43 -0
- package/dist/postgres/tenancy.js +71 -0
- package/dist/prompts/file.js +28 -11
- package/dist/protocols/a2a.d.ts +235 -0
- package/dist/protocols/a2a.js +386 -0
- package/dist/protocols/acp.d.ts +87 -0
- package/dist/protocols/acp.js +322 -0
- package/dist/protocols/ag-ui.d.ts +120 -0
- package/dist/protocols/ag-ui.js +180 -0
- package/dist/protocols/shared.d.ts +80 -0
- package/dist/protocols/shared.js +49 -0
- package/dist/providers/anthropic.js +1 -0
- package/dist/providers/cohere.js +2 -2
- package/dist/providers/google.js +2 -11
- package/dist/providers/openai.js +1 -0
- package/dist/rag/file-ingestion.js +15 -9
- package/dist/runtime/detect.d.ts +17 -0
- package/dist/runtime/detect.js +16 -0
- package/dist/runtime/index.d.ts +19 -0
- package/dist/runtime/index.js +7 -0
- package/dist/security/injection-detector.d.ts +2 -0
- package/dist/security/injection-detector.js +7 -1
- package/dist/security/input-guard.js +5 -2
- package/dist/security/output-guard.js +8 -4
- package/dist/security/pii-detector.js +22 -6
- package/dist/security/prompt-hardening.js +6 -2
- package/dist/security/semantic-injection-classifier.d.ts +2 -1
- package/dist/security/semantic-injection-classifier.js +31 -35
- package/dist/security/upload-scanner.d.ts +8 -0
- package/dist/security/upload-scanner.js +51 -9
- package/dist/server/deployments.d.ts +188 -11
- package/dist/server/deployments.js +474 -36
- package/dist/server/metrics.d.ts +2 -0
- package/dist/server/metrics.js +7 -0
- package/dist/server/runs.d.ts +18 -1
- package/dist/server/runs.js +66 -7
- package/dist/server/server.js +9 -1
- package/dist/server/state.d.ts +12 -2
- package/dist/server/state.js +15 -0
- package/dist/sqlite/operations.js +15 -4
- package/dist/sqlite/store.d.ts +7 -0
- package/dist/sqlite/store.js +28 -0
- package/dist/store/memory.d.ts +8 -0
- package/dist/store/memory.js +12 -1
- package/dist/store/redis.d.ts +13 -0
- package/dist/store/redis.js +38 -0
- package/dist/testing/record.js +2 -2
- package/dist/tracing/instrument.js +1 -0
- package/dist/tracing/stores.js +2 -1
- package/dist/tracing/tracer.d.ts +4 -0
- package/dist/tracing/tracer.js +8 -2
- package/dist/types/config.d.ts +20 -0
- package/dist/types/operations.d.ts +7 -0
- package/dist/types/server.d.ts +67 -2
- package/dist/types/store.d.ts +9 -0
- package/dist/utils/base64.d.ts +4 -0
- package/dist/utils/base64.js +21 -0
- package/dist/utils/checksums.d.ts +22 -0
- package/dist/utils/checksums.js +39 -0
- package/dist/utils/ids.d.ts +8 -0
- package/dist/utils/ids.js +26 -2
- package/dist/utils/sha256.d.ts +22 -0
- package/dist/utils/sha256.js +120 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-cjs/adapter-kit/contracts.d.ts +87 -0
- package/dist-cjs/adapter-kit/contracts.js +243 -0
- package/dist-cjs/adapter-kit/errors.d.ts +39 -0
- package/dist-cjs/adapter-kit/errors.js +57 -0
- package/dist-cjs/adapter-kit/index.d.ts +144 -0
- package/dist-cjs/adapter-kit/index.js +194 -0
- package/dist-cjs/adapter-kit/semver.d.ts +12 -0
- package/dist-cjs/adapter-kit/semver.js +59 -0
- package/dist-cjs/agent/middleware/pii.js +4 -35
- package/dist-cjs/context-hub/hub.d.ts +53 -0
- package/dist-cjs/context-hub/hub.js +110 -4
- package/dist-cjs/context-hub/index.d.ts +2 -1
- package/dist-cjs/context-hub/index.js +7 -1
- package/dist-cjs/context-hub/signing.d.ts +95 -0
- package/dist-cjs/context-hub/signing.js +96 -0
- package/dist-cjs/core/secure-stream.js +4 -3
- package/dist-cjs/doctor/index.d.ts +4 -2
- package/dist-cjs/doctor/index.js +29 -0
- package/dist-cjs/embeddings/adapters.js +2 -1
- package/dist-cjs/evals/judge.js +2 -5
- package/dist-cjs/evaluate/cache.d.ts +0 -14
- package/dist-cjs/evaluate/cache.js +1 -65
- package/dist-cjs/evaluate/file-cache.d.ts +15 -0
- package/dist-cjs/evaluate/file-cache.js +67 -0
- package/dist-cjs/evaluate/index.d.ts +2 -1
- package/dist-cjs/evaluate/index.js +3 -2
- package/dist-cjs/evaluate/review.js +2 -2
- package/dist-cjs/evaluate/run.js +2 -2
- package/dist-cjs/evaluate/threads.d.ts +187 -0
- package/dist-cjs/evaluate/threads.js +198 -0
- package/dist-cjs/evaluate/version.js +3 -3
- package/dist-cjs/graph/graph.js +2 -2
- package/dist-cjs/graph/node-cache.js +3 -3
- package/dist-cjs/hallucination/rag.d.ts +4 -1
- package/dist-cjs/hallucination/rag.js +6 -1
- package/dist-cjs/images/asset-stores.js +2 -3
- package/dist-cjs/images/asset-support.js +2 -2
- package/dist-cjs/images/assets.js +1 -2
- package/dist-cjs/images/evals.js +4 -4
- package/dist-cjs/insights/proposals.js +1 -2
- package/dist-cjs/insights/regressions.d.ts +26 -2
- package/dist-cjs/insights/regressions.js +129 -3
- package/dist-cjs/loaders/git.js +8 -1
- package/dist-cjs/loaders/web.js +7 -1
- package/dist-cjs/mcp/client.d.ts +14 -0
- package/dist-cjs/mcp/client.js +60 -1
- package/dist-cjs/mcp/registry.d.ts +6 -0
- package/dist-cjs/mcp/registry.js +7 -0
- package/dist-cjs/operations/adapters.d.ts +2 -1
- package/dist-cjs/operations/runner.js +2 -0
- package/dist-cjs/operations/stats.js +11 -4
- package/dist-cjs/postgres/index.d.ts +2 -1
- package/dist-cjs/postgres/index.js +8 -1
- package/dist-cjs/postgres/operations.js +11 -4
- package/dist-cjs/postgres/store.d.ts +7 -0
- package/dist-cjs/postgres/store.js +21 -0
- package/dist-cjs/postgres/tenancy.d.ts +43 -0
- package/dist-cjs/postgres/tenancy.js +110 -0
- package/dist-cjs/prompts/file.js +28 -11
- package/dist-cjs/protocols/a2a.d.ts +235 -0
- package/dist-cjs/protocols/a2a.js +393 -0
- package/dist-cjs/protocols/acp.d.ts +87 -0
- package/dist-cjs/protocols/acp.js +326 -0
- package/dist-cjs/protocols/ag-ui.d.ts +120 -0
- package/dist-cjs/protocols/ag-ui.js +186 -0
- package/dist-cjs/protocols/shared.d.ts +80 -0
- package/dist-cjs/protocols/shared.js +59 -0
- package/dist-cjs/providers/anthropic.js +1 -0
- package/dist-cjs/providers/cohere.js +2 -2
- package/dist-cjs/providers/google.js +3 -12
- package/dist-cjs/providers/openai.js +1 -0
- package/dist-cjs/rag/file-ingestion.js +14 -8
- package/dist-cjs/runtime/detect.d.ts +17 -0
- package/dist-cjs/runtime/detect.js +19 -0
- package/dist-cjs/runtime/index.d.ts +19 -0
- package/dist-cjs/runtime/index.js +31 -0
- package/dist-cjs/security/injection-detector.d.ts +2 -0
- package/dist-cjs/security/injection-detector.js +7 -1
- package/dist-cjs/security/input-guard.js +5 -2
- package/dist-cjs/security/output-guard.js +8 -4
- package/dist-cjs/security/pii-detector.js +22 -6
- package/dist-cjs/security/prompt-hardening.js +6 -2
- package/dist-cjs/security/semantic-injection-classifier.d.ts +2 -1
- package/dist-cjs/security/semantic-injection-classifier.js +31 -35
- package/dist-cjs/security/upload-scanner.d.ts +8 -0
- package/dist-cjs/security/upload-scanner.js +53 -9
- package/dist-cjs/server/deployments.d.ts +188 -11
- package/dist-cjs/server/deployments.js +474 -36
- package/dist-cjs/server/metrics.d.ts +2 -0
- package/dist-cjs/server/metrics.js +8 -0
- package/dist-cjs/server/runs.d.ts +18 -1
- package/dist-cjs/server/runs.js +65 -6
- package/dist-cjs/server/server.js +9 -1
- package/dist-cjs/server/state.d.ts +12 -2
- package/dist-cjs/server/state.js +15 -0
- package/dist-cjs/sqlite/operations.js +15 -4
- package/dist-cjs/sqlite/store.d.ts +7 -0
- package/dist-cjs/sqlite/store.js +28 -0
- package/dist-cjs/store/memory.d.ts +8 -0
- package/dist-cjs/store/memory.js +12 -1
- package/dist-cjs/store/redis.d.ts +13 -0
- package/dist-cjs/store/redis.js +38 -0
- package/dist-cjs/testing/record.js +2 -2
- package/dist-cjs/tracing/instrument.js +1 -0
- package/dist-cjs/tracing/stores.js +2 -1
- package/dist-cjs/tracing/tracer.d.ts +4 -0
- package/dist-cjs/tracing/tracer.js +8 -2
- package/dist-cjs/types/config.d.ts +20 -0
- package/dist-cjs/types/operations.d.ts +7 -0
- package/dist-cjs/types/server.d.ts +67 -2
- package/dist-cjs/types/store.d.ts +9 -0
- package/dist-cjs/utils/base64.d.ts +4 -0
- package/dist-cjs/utils/base64.js +25 -0
- package/dist-cjs/utils/checksums.d.ts +22 -0
- package/dist-cjs/utils/checksums.js +46 -0
- package/dist-cjs/utils/ids.d.ts +8 -0
- package/dist-cjs/utils/ids.js +28 -2
- package/dist-cjs/utils/sha256.d.ts +22 -0
- package/dist-cjs/utils/sha256.js +125 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/llms.txt +5 -2
- 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
|
+
`ignore 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.
|
|
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
|
|
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.
|
|
264
|
-
|
|
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.
|