nexus-ai-pro 2.4.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 +85 -0
- package/CHANGELOG.md +147 -1
- package/README.md +8 -3
- package/SECURITY.md +78 -9
- 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/doctor/index.d.ts +4 -2
- package/dist/doctor/index.js +29 -0
- package/dist/evaluate/threads.d.ts +187 -0
- package/dist/evaluate/threads.js +187 -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/server/deployments.d.ts +127 -12
- package/dist/server/deployments.js +402 -37
- package/dist/server/metrics.d.ts +2 -0
- package/dist/server/metrics.js +7 -0
- package/dist/server/runs.d.ts +9 -1
- package/dist/server/runs.js +46 -7
- package/dist/server/server.js +4 -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/tracing/instrument.js +1 -0
- package/dist/tracing/tracer.d.ts +4 -0
- package/dist/tracing/tracer.js +6 -0
- package/dist/types/operations.d.ts +7 -0
- package/dist/types/server.d.ts +61 -2
- package/dist/types/store.d.ts +9 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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/doctor/index.d.ts +4 -2
- package/dist-cjs/doctor/index.js +29 -0
- package/dist-cjs/evaluate/threads.d.ts +187 -0
- package/dist-cjs/evaluate/threads.js +198 -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/server/deployments.d.ts +127 -12
- package/dist-cjs/server/deployments.js +402 -37
- package/dist-cjs/server/metrics.d.ts +2 -0
- package/dist-cjs/server/metrics.js +8 -0
- package/dist-cjs/server/runs.d.ts +9 -1
- package/dist-cjs/server/runs.js +45 -6
- package/dist-cjs/server/server.js +4 -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/tracing/instrument.js +1 -0
- package/dist-cjs/tracing/tracer.d.ts +4 -0
- package/dist-cjs/tracing/tracer.js +6 -0
- package/dist-cjs/types/operations.d.ts +7 -0
- package/dist-cjs/types/server.d.ts +61 -2
- package/dist-cjs/types/store.d.ts +9 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +27 -1
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NEXUS_VERSION = '2.
|
|
1
|
+
export const NEXUS_VERSION = '2.5.0';
|
|
@@ -2,6 +2,7 @@ import type { GateResult } from '../prompts/errors.js';
|
|
|
2
2
|
import type { PromptRegistry } from '../prompts/registry.js';
|
|
3
3
|
import type { PromptHistoryEntry, PromptLabel, PromptStore, PromptVersion, RenderedPrompt, RenderOptions } from '../types/prompts.js';
|
|
4
4
|
import { type ContextDiff } from './diff.js';
|
|
5
|
+
import { type ContextKeyring, type ContextSignature, type ContextSigner } from './signing.js';
|
|
5
6
|
/** A prompt a bundle uses, pinned to one content version of the prompt registry. */
|
|
6
7
|
export interface ContextPromptPin {
|
|
7
8
|
/** The prompt's name in the registry. */
|
|
@@ -61,6 +62,8 @@ export interface ContextBundle extends ContextBundleDefinition {
|
|
|
61
62
|
author?: string;
|
|
62
63
|
/** The version that was newest before it. */
|
|
63
64
|
parent?: string;
|
|
65
|
+
/** Signatures over its content and its pinned prompts' content. Not part of the version. */
|
|
66
|
+
signatures?: ContextSignature[];
|
|
64
67
|
}
|
|
65
68
|
/** Which bundle version is being used, as experiments and traces record it. */
|
|
66
69
|
export interface ContextReference {
|
|
@@ -125,9 +128,33 @@ export interface ContextHubOptions {
|
|
|
125
128
|
gates?: Record<string, readonly ContextPromotionGate[]>;
|
|
126
129
|
/** Called after every recorded change. */
|
|
127
130
|
onChange?: (entry: ContextHistoryEntry) => void | Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* Signs every new version, over its content and its pinned prompts' content, so another hub can
|
|
133
|
+
* check where it came from. Signing a bundle that pins prompts needs `prompts`.
|
|
134
|
+
*/
|
|
135
|
+
signer?: ContextSigner;
|
|
136
|
+
/**
|
|
137
|
+
* The keys whose signatures this hub trusts, for `verify()`, and for `requireSignature`. A signature
|
|
138
|
+
* by a key the keyring does not hold is kept, but never trusted.
|
|
139
|
+
*/
|
|
140
|
+
keyring?: ContextKeyring;
|
|
141
|
+
/**
|
|
142
|
+
* Refuses any bundle without a valid signature by a key in `keyring`: on import, and whenever a
|
|
143
|
+
* version is served through `resolve()` or `renderPrompt()`.
|
|
144
|
+
*/
|
|
145
|
+
requireSignature?: boolean;
|
|
128
146
|
/** Replaces the system clock, for tests. */
|
|
129
147
|
now?: () => Date;
|
|
130
148
|
}
|
|
149
|
+
/** What `ContextHub.verify()` found. */
|
|
150
|
+
export interface ContextVerification {
|
|
151
|
+
/** Whether a key in the keyring signed exactly this content. */
|
|
152
|
+
trusted: boolean;
|
|
153
|
+
/** The digest the content has now. */
|
|
154
|
+
digest: string;
|
|
155
|
+
/** The signature that verified, when one did. */
|
|
156
|
+
signature?: ContextSignature;
|
|
157
|
+
}
|
|
131
158
|
/** Base class for context hub errors, each with a stable `code`. */
|
|
132
159
|
export declare class ContextHubError extends Error {
|
|
133
160
|
/** Stable code, such as `CONTEXT_NOT_FOUND`. */
|
|
@@ -160,6 +187,18 @@ export declare class ContextPromotionError extends ContextHubError {
|
|
|
160
187
|
/** Every gate's verdict. */
|
|
161
188
|
results: GateResult[]);
|
|
162
189
|
}
|
|
190
|
+
/** Raised when a bundle has no signature the hub trusts, or its signatures do not match its content. */
|
|
191
|
+
export declare class ContextSignatureError extends ContextHubError {
|
|
192
|
+
/** The bundle. */
|
|
193
|
+
readonly bundle: string;
|
|
194
|
+
/** The version. */
|
|
195
|
+
readonly version: string;
|
|
196
|
+
constructor(
|
|
197
|
+
/** The bundle. */
|
|
198
|
+
bundle: string,
|
|
199
|
+
/** The version. */
|
|
200
|
+
version: string, reason: string);
|
|
201
|
+
}
|
|
163
202
|
/** Raised when a bundle definition, a pin, or an import is invalid. */
|
|
164
203
|
export declare class ContextDefinitionError extends ContextHubError {
|
|
165
204
|
constructor(message: string);
|
|
@@ -182,6 +221,8 @@ export declare class ContextHub {
|
|
|
182
221
|
/** Where versions, labels, and history live. */
|
|
183
222
|
readonly store: PromptStore;
|
|
184
223
|
private readonly now;
|
|
224
|
+
/** Versions already verified as trusted, which never change. */
|
|
225
|
+
private readonly trusted;
|
|
185
226
|
constructor(options?: ContextHubOptions);
|
|
186
227
|
/**
|
|
187
228
|
* Commits a bundle and returns its version. Unchanged content returns the existing version and
|
|
@@ -193,8 +234,15 @@ export declare class ContextHub {
|
|
|
193
234
|
author?: string;
|
|
194
235
|
label?: string | readonly string[];
|
|
195
236
|
}): Promise<ContextBundle>;
|
|
237
|
+
private commitSigned;
|
|
196
238
|
/** A version by content version, label, or `latest`, the default. Throws `ContextNotFoundError` when there is none. */
|
|
197
239
|
get(name: string, ref?: string): Promise<ContextBundle>;
|
|
240
|
+
/**
|
|
241
|
+
* Whether a key in the hub's keyring signed exactly this bundle's content, and its pinned prompts'
|
|
242
|
+
* content as the prompt registry holds them. A version's answer never changes, so it is checked
|
|
243
|
+
* once.
|
|
244
|
+
*/
|
|
245
|
+
verify(bundle: ContextBundle): Promise<ContextVerification>;
|
|
198
246
|
/** A version with the reference experiments and traces record for it. */
|
|
199
247
|
resolve(name: string, ref?: string): Promise<{
|
|
200
248
|
bundle: ContextBundle;
|
|
@@ -262,6 +310,11 @@ export declare class ContextHub {
|
|
|
262
310
|
label?: string | readonly string[];
|
|
263
311
|
by?: string;
|
|
264
312
|
}): Promise<ContextBundle>;
|
|
313
|
+
/** Refuses a bundle without a trusted signature, when the hub requires one. */
|
|
314
|
+
private assertTrusted;
|
|
315
|
+
private check;
|
|
316
|
+
/** What a signature over this bundle covers, with its pinned prompts read from the registry. */
|
|
317
|
+
private signedContent;
|
|
265
318
|
private read;
|
|
266
319
|
private pointer;
|
|
267
320
|
private record;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ContextHub = exports.ContextDefinitionError = exports.ContextPromotionError = exports.ContextConflictError = exports.ContextNotFoundError = exports.ContextHubError = void 0;
|
|
3
|
+
exports.ContextHub = exports.ContextDefinitionError = exports.ContextSignatureError = exports.ContextPromotionError = exports.ContextConflictError = exports.ContextNotFoundError = exports.ContextHubError = void 0;
|
|
4
4
|
exports.contextVersion = contextVersion;
|
|
5
5
|
const memory_js_1 = require("../prompts/memory.js");
|
|
6
6
|
const version_js_1 = require("../prompts/version.js");
|
|
7
7
|
const diff_js_1 = require("./diff.js");
|
|
8
|
+
const signing_js_1 = require("./signing.js");
|
|
8
9
|
class ContextHubError extends Error {
|
|
9
10
|
code;
|
|
10
11
|
constructor(message, code) {
|
|
@@ -44,6 +45,17 @@ class ContextPromotionError extends ContextHubError {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
exports.ContextPromotionError = ContextPromotionError;
|
|
48
|
+
class ContextSignatureError extends ContextHubError {
|
|
49
|
+
bundle;
|
|
50
|
+
version;
|
|
51
|
+
constructor(bundle, version, reason) {
|
|
52
|
+
super(`Context bundle "${bundle}" ${version}: ${reason}`, 'CONTEXT_SIGNATURE');
|
|
53
|
+
this.bundle = bundle;
|
|
54
|
+
this.version = version;
|
|
55
|
+
this.name = 'ContextSignatureError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.ContextSignatureError = ContextSignatureError;
|
|
47
59
|
class ContextDefinitionError extends ContextHubError {
|
|
48
60
|
constructor(message) {
|
|
49
61
|
super(message, 'CONTEXT_DEFINITION_ERROR');
|
|
@@ -72,12 +84,19 @@ class ContextHub {
|
|
|
72
84
|
options;
|
|
73
85
|
store;
|
|
74
86
|
now;
|
|
87
|
+
trusted = new Map();
|
|
75
88
|
constructor(options = {}) {
|
|
76
89
|
this.options = options;
|
|
90
|
+
if (options.requireSignature && !options.keyring) {
|
|
91
|
+
throw new TypeError('requireSignature needs a keyring of the keys the hub trusts');
|
|
92
|
+
}
|
|
77
93
|
this.store = options.store ?? new memory_js_1.MemoryPromptStore();
|
|
78
94
|
this.now = options.now ?? (() => new Date());
|
|
79
95
|
}
|
|
80
96
|
async commit(definition, options = {}) {
|
|
97
|
+
return this.commitSigned(definition, options, []);
|
|
98
|
+
}
|
|
99
|
+
async commitSigned(definition, options, inherited) {
|
|
81
100
|
validate(definition);
|
|
82
101
|
if (this.options.prompts) {
|
|
83
102
|
for (const [role, pin] of Object.entries(definition.prompts ?? {})) {
|
|
@@ -101,6 +120,13 @@ class ContextHub {
|
|
|
101
120
|
...(options.author === undefined ? {} : { author: options.author }),
|
|
102
121
|
...(newest ? { parent: newest.version } : {}),
|
|
103
122
|
};
|
|
123
|
+
const signatures = [...inherited];
|
|
124
|
+
if (this.options.signer) {
|
|
125
|
+
const digest = await (0, signing_js_1.contextDigest)(await this.signedContent(definition));
|
|
126
|
+
signatures.push(await (0, signing_js_1.signDigest)(this.options.signer, digest, this.now()));
|
|
127
|
+
}
|
|
128
|
+
if (signatures.length > 0)
|
|
129
|
+
stored.signatures = signatures;
|
|
104
130
|
await this.store.saveVersion(toStored(stored));
|
|
105
131
|
await this.record({ name: definition.name, action: 'commit', version: id, by: options.author });
|
|
106
132
|
}
|
|
@@ -127,8 +153,19 @@ class ContextHub {
|
|
|
127
153
|
throw new ContextNotFoundError(name, ref);
|
|
128
154
|
return found;
|
|
129
155
|
}
|
|
156
|
+
async verify(bundle) {
|
|
157
|
+
const key = `${bundle.name}@${bundle.version}`;
|
|
158
|
+
let pending = this.trusted.get(key);
|
|
159
|
+
if (!pending) {
|
|
160
|
+
pending = this.check(bundle, await this.signedContent(bundle));
|
|
161
|
+
this.trusted.set(key, pending);
|
|
162
|
+
pending.catch(() => this.trusted.delete(key));
|
|
163
|
+
}
|
|
164
|
+
return pending;
|
|
165
|
+
}
|
|
130
166
|
async resolve(name, ref = 'latest') {
|
|
131
167
|
const bundle = await this.get(name, ref);
|
|
168
|
+
await this.assertTrusted(bundle);
|
|
132
169
|
return {
|
|
133
170
|
bundle,
|
|
134
171
|
reference: {
|
|
@@ -142,6 +179,7 @@ class ContextHub {
|
|
|
142
179
|
const registry = this.options.prompts;
|
|
143
180
|
if (!registry)
|
|
144
181
|
throw new ContextDefinitionError('Rendering a pinned prompt needs the hub to have a prompt registry');
|
|
182
|
+
await this.assertTrusted(bundle);
|
|
145
183
|
const pin = bundle.prompts?.[role];
|
|
146
184
|
if (!pin)
|
|
147
185
|
throw new ContextNotFoundError(bundle.name, `prompt role "${role}"`);
|
|
@@ -268,15 +306,52 @@ class ContextHub {
|
|
|
268
306
|
throw new ContextDefinitionError(`Prompt ${prompt.name} was exported as ${version} but its content is ${committed.version}`);
|
|
269
307
|
}
|
|
270
308
|
}
|
|
271
|
-
const { version, createdAt: _createdAt, parent: _parent, message, author, ...definition } = exported.bundle;
|
|
309
|
+
const { version, createdAt: _createdAt, parent: _parent, message, author, signatures = [], ...definition } = exported.bundle;
|
|
272
310
|
if ((await contextVersion(definition)) !== version) {
|
|
273
311
|
throw new ContextDefinitionError(`Bundle ${definition.name} was exported as ${version} but its content has changed`);
|
|
274
312
|
}
|
|
275
|
-
|
|
313
|
+
const digest = await (0, signing_js_1.contextDigest)(signedContentOf(definition, exportedPrompts(exported)));
|
|
314
|
+
const claims = signatures.filter((signature) => signature?.digest === digest);
|
|
315
|
+
if (this.options.requireSignature && this.options.keyring) {
|
|
316
|
+
const signed = await (0, signing_js_1.verifiedBy)(this.options.keyring, digest, claims);
|
|
317
|
+
if (!signed) {
|
|
318
|
+
throw new ContextSignatureError(definition.name, version, signatures.length > 0
|
|
319
|
+
? 'no signature verifies against its content with a trusted key'
|
|
320
|
+
: 'it is not signed, and this hub requires a signature');
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return this.commitSigned(definition, {
|
|
276
324
|
message: message ?? 'imported',
|
|
277
325
|
...((options.by ?? author) === undefined ? {} : { author: options.by ?? author }),
|
|
278
326
|
...(options.label === undefined ? {} : { label: options.label }),
|
|
279
|
-
});
|
|
327
|
+
}, claims);
|
|
328
|
+
}
|
|
329
|
+
async assertTrusted(bundle) {
|
|
330
|
+
if (!this.options.requireSignature)
|
|
331
|
+
return;
|
|
332
|
+
const verification = await this.verify(bundle);
|
|
333
|
+
if (!verification.trusted) {
|
|
334
|
+
throw new ContextSignatureError(bundle.name, bundle.version, bundle.signatures?.length
|
|
335
|
+
? 'no signature verifies against its content with a trusted key'
|
|
336
|
+
: 'it is not signed, and this hub requires a signature');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
async check(bundle, content) {
|
|
340
|
+
const digest = await (0, signing_js_1.contextDigest)(content);
|
|
341
|
+
const signature = this.options.keyring
|
|
342
|
+
? await (0, signing_js_1.verifiedBy)(this.options.keyring, digest, bundle.signatures)
|
|
343
|
+
: undefined;
|
|
344
|
+
return { trusted: signature !== undefined, digest, ...(signature ? { signature } : {}) };
|
|
345
|
+
}
|
|
346
|
+
async signedContent(definition) {
|
|
347
|
+
const pins = Object.entries(definition.prompts ?? {});
|
|
348
|
+
if (pins.length > 0 && !this.options.prompts) {
|
|
349
|
+
throw new ContextDefinitionError(`Bundle "${definition.name}" pins prompts; signing or verifying it needs the hub to have a prompt registry`);
|
|
350
|
+
}
|
|
351
|
+
const prompts = [];
|
|
352
|
+
for (const [, pin] of pins)
|
|
353
|
+
prompts.push(await this.options.prompts.get(pin.name, pin.version));
|
|
354
|
+
return signedContentOf(definition, prompts);
|
|
280
355
|
}
|
|
281
356
|
async read(name, version) {
|
|
282
357
|
const stored = await this.store.getVersion(name, version);
|
|
@@ -314,6 +389,37 @@ function validate(definition) {
|
|
|
314
389
|
}
|
|
315
390
|
}
|
|
316
391
|
}
|
|
392
|
+
function signedContentOf(definition, prompts) {
|
|
393
|
+
const byPin = new Map(prompts.map((prompt) => [`${prompt.name}@${prompt.version}`, prompt]));
|
|
394
|
+
const pinned = {};
|
|
395
|
+
for (const [role, pin] of Object.entries(definition.prompts ?? {})) {
|
|
396
|
+
const prompt = byPin.get(`${pin.name}@${pin.version}`);
|
|
397
|
+
if (!prompt)
|
|
398
|
+
throw new ContextDefinitionError(`Bundle "${definition.name}" pins ${role} to ${pin.name}@${pin.version}, which is not at hand`);
|
|
399
|
+
pinned[role] = {
|
|
400
|
+
name: prompt.name,
|
|
401
|
+
messages: prompt.messages,
|
|
402
|
+
partials: prompt.partials,
|
|
403
|
+
config: prompt.config,
|
|
404
|
+
defaults: prompt.defaults,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
bundle: {
|
|
409
|
+
name: definition.name,
|
|
410
|
+
description: definition.description,
|
|
411
|
+
prompts: definition.prompts,
|
|
412
|
+
instructions: definition.instructions,
|
|
413
|
+
tools: definition.tools,
|
|
414
|
+
skills: definition.skills,
|
|
415
|
+
config: definition.config,
|
|
416
|
+
},
|
|
417
|
+
prompts: pinned,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function exportedPrompts(exported) {
|
|
421
|
+
return exported.prompts;
|
|
422
|
+
}
|
|
317
423
|
function toStored(bundle) {
|
|
318
424
|
return { ...bundle, messages: [], variables: [] };
|
|
319
425
|
}
|
|
@@ -4,8 +4,9 @@ import type { Dataset, DatasetExample, Evaluator, Experiment } from '../types/ev
|
|
|
4
4
|
import type { ToolDefinition } from '../types/messages.js';
|
|
5
5
|
import type { ContextBundle, ContextPromotionGate } from './hub.js';
|
|
6
6
|
export { type ContextChange, type ContextDiff, diffContexts, formatContextDiff } from './diff.js';
|
|
7
|
-
export { type ContextBundle, type ContextBundleDefinition, type ContextBundleExport, ContextConflictError, ContextDefinitionError, type ContextHistoryEntry, ContextHub, ContextHubError, type ContextHubOptions, type ContextLabel, ContextNotFoundError, type ContextPromotionContext, ContextPromotionError, type ContextPromotionGate, type ContextPromotionResult, type ContextPromptPin, type ContextReference, type ContextSkill, type ContextTool, contextVersion, } from './hub.js';
|
|
7
|
+
export { type ContextBundle, type ContextBundleDefinition, type ContextBundleExport, ContextConflictError, ContextDefinitionError, type ContextHistoryEntry, ContextHub, ContextHubError, type ContextHubOptions, type ContextLabel, ContextNotFoundError, type ContextPromotionContext, ContextPromotionError, type ContextPromotionGate, ContextSignatureError, type ContextVerification, type ContextPromotionResult, type ContextPromptPin, type ContextReference, type ContextSkill, type ContextTool, contextVersion, } from './hub.js';
|
|
8
8
|
export type { ExperimentGateOptions } from '../prompts/gates.js';
|
|
9
|
+
export { type ContextCryptoKey, type ContextJsonWebKey, type ContextKeyring, type ContextSignature, type ContextSignedContent, type ContextSigner, type ContextSigningKey, contextDigest, ed25519Keyring, ed25519Signer, generateContextKey, } from './signing.js';
|
|
9
10
|
/**
|
|
10
11
|
* A bundle's instructions joined into one system text, in the order the bundle lists them, or only
|
|
11
12
|
* the ones named in `include`, in that order.
|
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.contextVersion = exports.ContextPromotionError = exports.ContextNotFoundError = exports.ContextHubError = exports.ContextHub = exports.ContextDefinitionError = exports.ContextConflictError = exports.formatContextDiff = exports.diffContexts = void 0;
|
|
36
|
+
exports.generateContextKey = exports.ed25519Signer = exports.ed25519Keyring = exports.contextDigest = exports.contextVersion = exports.ContextSignatureError = exports.ContextPromotionError = exports.ContextNotFoundError = exports.ContextHubError = exports.ContextHub = exports.ContextDefinitionError = exports.ContextConflictError = exports.formatContextDiff = exports.diffContexts = void 0;
|
|
37
37
|
exports.contextInstructions = contextInstructions;
|
|
38
38
|
exports.bindTools = bindTools;
|
|
39
39
|
exports.evaluateContext = evaluateContext;
|
|
@@ -51,7 +51,13 @@ Object.defineProperty(exports, "ContextHub", { enumerable: true, get: function (
|
|
|
51
51
|
Object.defineProperty(exports, "ContextHubError", { enumerable: true, get: function () { return hub_js_1.ContextHubError; } });
|
|
52
52
|
Object.defineProperty(exports, "ContextNotFoundError", { enumerable: true, get: function () { return hub_js_1.ContextNotFoundError; } });
|
|
53
53
|
Object.defineProperty(exports, "ContextPromotionError", { enumerable: true, get: function () { return hub_js_1.ContextPromotionError; } });
|
|
54
|
+
Object.defineProperty(exports, "ContextSignatureError", { enumerable: true, get: function () { return hub_js_1.ContextSignatureError; } });
|
|
54
55
|
Object.defineProperty(exports, "contextVersion", { enumerable: true, get: function () { return hub_js_1.contextVersion; } });
|
|
56
|
+
var signing_js_1 = require("./signing.js");
|
|
57
|
+
Object.defineProperty(exports, "contextDigest", { enumerable: true, get: function () { return signing_js_1.contextDigest; } });
|
|
58
|
+
Object.defineProperty(exports, "ed25519Keyring", { enumerable: true, get: function () { return signing_js_1.ed25519Keyring; } });
|
|
59
|
+
Object.defineProperty(exports, "ed25519Signer", { enumerable: true, get: function () { return signing_js_1.ed25519Signer; } });
|
|
60
|
+
Object.defineProperty(exports, "generateContextKey", { enumerable: true, get: function () { return signing_js_1.generateContextKey; } });
|
|
55
61
|
function contextInstructions(bundle, options = {}) {
|
|
56
62
|
const names = options.include ?? Object.keys(bundle.instructions ?? {});
|
|
57
63
|
return names
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { PromptDefinition } from '../types/prompts.js';
|
|
2
|
+
/** A JSON Web Key, as Web Crypto exports one. */
|
|
3
|
+
export interface ContextJsonWebKey {
|
|
4
|
+
/** The key type: `OKP` for Ed25519. */
|
|
5
|
+
kty?: string;
|
|
6
|
+
/** The curve: `Ed25519`. */
|
|
7
|
+
crv?: string;
|
|
8
|
+
/** The public key, in base64url. */
|
|
9
|
+
x?: string;
|
|
10
|
+
/** The private key, in base64url, on a private key only. */
|
|
11
|
+
d?: string;
|
|
12
|
+
/** Anything else the exporter wrote. */
|
|
13
|
+
[field: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
/** A Web Crypto key, as `crypto.subtle` returns one. Any runtime's `CryptoKey` is one. */
|
|
16
|
+
export interface ContextCryptoKey {
|
|
17
|
+
/** Its algorithm. */
|
|
18
|
+
readonly algorithm: unknown;
|
|
19
|
+
/** What it may do. */
|
|
20
|
+
readonly usages: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
/** One signature on a bundle version. Stored with it, and carried by its export. */
|
|
23
|
+
export interface ContextSignature {
|
|
24
|
+
/** The id of the key that signed, which a keyring looks up. */
|
|
25
|
+
keyId: string;
|
|
26
|
+
/** Always `Ed25519`. */
|
|
27
|
+
algorithm: 'Ed25519';
|
|
28
|
+
/**
|
|
29
|
+
* What was signed: `sha256:` and the full SHA-256, in hex, of the bundle's content and the content
|
|
30
|
+
* of every prompt it pins. A bundle's version is only the first 12 hex digits, enough to name a
|
|
31
|
+
* version but not to stand behind a signature.
|
|
32
|
+
*/
|
|
33
|
+
digest: string;
|
|
34
|
+
/** The signature over the digest, in base64url. */
|
|
35
|
+
signature: string;
|
|
36
|
+
/** ISO-8601 time it was signed. */
|
|
37
|
+
signedAt: string;
|
|
38
|
+
}
|
|
39
|
+
/** Signs bundle digests with a private key, under a key id. */
|
|
40
|
+
export interface ContextSigner {
|
|
41
|
+
/** The key's id, recorded on each signature so a keyring finds the public key. */
|
|
42
|
+
keyId: string;
|
|
43
|
+
/** Signs bytes. */
|
|
44
|
+
sign(data: Uint8Array): Promise<Uint8Array> | Uint8Array;
|
|
45
|
+
}
|
|
46
|
+
/** Verifies signatures by key id. A signature by a key the keyring does not hold never verifies. */
|
|
47
|
+
export interface ContextKeyring {
|
|
48
|
+
/** Whether `signature` is `keyId`'s signature over `data`. */
|
|
49
|
+
verify(keyId: string, data: Uint8Array, signature: Uint8Array): Promise<boolean> | boolean;
|
|
50
|
+
}
|
|
51
|
+
/** A new signing key, with both halves as JSON Web Keys to keep in a secret store. */
|
|
52
|
+
export interface ContextSigningKey {
|
|
53
|
+
/** The key's id. */
|
|
54
|
+
keyId: string;
|
|
55
|
+
/** Signs with the new key. */
|
|
56
|
+
signer: ContextSigner;
|
|
57
|
+
/** The public half, for every keyring that should trust it. */
|
|
58
|
+
publicKey: ContextJsonWebKey;
|
|
59
|
+
/** The private half. Keep it where the signer runs, and nowhere else. */
|
|
60
|
+
privateKey: ContextJsonWebKey;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Creates an Ed25519 signing key through Web Crypto, so on any runtime. The key id defaults to the
|
|
64
|
+
* first 16 hex digits of the public key's SHA-256, which is stable for the key and names nothing
|
|
65
|
+
* else.
|
|
66
|
+
*/
|
|
67
|
+
export declare function generateContextKey(keyId?: string): Promise<ContextSigningKey>;
|
|
68
|
+
/** A signer from an Ed25519 private key, as a JSON Web Key or a Web Crypto key. */
|
|
69
|
+
export declare function ed25519Signer(keyId: string, privateKey: ContextJsonWebKey | ContextCryptoKey): Promise<ContextSigner>;
|
|
70
|
+
/** A keyring of trusted Ed25519 public keys, by key id. */
|
|
71
|
+
export declare function ed25519Keyring(keys: Record<string, ContextJsonWebKey | ContextCryptoKey>): Promise<ContextKeyring>;
|
|
72
|
+
/** What a bundle signature covers: the bundle's versioned content, and its pinned prompts' content. */
|
|
73
|
+
export interface ContextSignedContent {
|
|
74
|
+
/** The bundle's name and the content its version covers. */
|
|
75
|
+
bundle: {
|
|
76
|
+
name: string;
|
|
77
|
+
description?: string;
|
|
78
|
+
prompts?: Record<string, {
|
|
79
|
+
name: string;
|
|
80
|
+
version: string;
|
|
81
|
+
}>;
|
|
82
|
+
instructions?: Record<string, string>;
|
|
83
|
+
tools?: unknown;
|
|
84
|
+
skills?: unknown;
|
|
85
|
+
config?: Record<string, unknown>;
|
|
86
|
+
};
|
|
87
|
+
/** Each pinned prompt's content, by role: what its own version covers. */
|
|
88
|
+
prompts: Record<string, Pick<PromptDefinition, 'name' | 'messages' | 'partials' | 'config' | 'defaults'>>;
|
|
89
|
+
}
|
|
90
|
+
/** The digest a signature covers, as `sha256:` and 64 hex digits. */
|
|
91
|
+
export declare function contextDigest(content: ContextSignedContent): Promise<string>;
|
|
92
|
+
/** Signs a digest. */
|
|
93
|
+
export declare function signDigest(signer: ContextSigner, digest: string, at: Date): Promise<ContextSignature>;
|
|
94
|
+
/** The first signature that is a trusted key's signature over exactly this digest, if any. */
|
|
95
|
+
export declare function verifiedBy(keyring: ContextKeyring, digest: string, signatures?: readonly ContextSignature[]): Promise<ContextSignature | undefined>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateContextKey = generateContextKey;
|
|
4
|
+
exports.ed25519Signer = ed25519Signer;
|
|
5
|
+
exports.ed25519Keyring = ed25519Keyring;
|
|
6
|
+
exports.contextDigest = contextDigest;
|
|
7
|
+
exports.signDigest = signDigest;
|
|
8
|
+
exports.verifiedBy = verifiedBy;
|
|
9
|
+
const version_js_1 = require("../prompts/version.js");
|
|
10
|
+
const base64_js_1 = require("../utils/base64.js");
|
|
11
|
+
const subtle = () => globalThis.crypto.subtle;
|
|
12
|
+
const ED25519 = { name: 'Ed25519' };
|
|
13
|
+
async function generateContextKey(keyId) {
|
|
14
|
+
const pair = await subtle().generateKey(ED25519, true, ['sign', 'verify']);
|
|
15
|
+
const publicKey = await subtle().exportKey('jwk', pair.publicKey);
|
|
16
|
+
const privateKey = await subtle().exportKey('jwk', pair.privateKey);
|
|
17
|
+
const id = keyId ?? (await keyIdOf(publicKey));
|
|
18
|
+
return { keyId: id, signer: await ed25519Signer(id, pair.privateKey), publicKey, privateKey };
|
|
19
|
+
}
|
|
20
|
+
async function ed25519Signer(keyId, privateKey) {
|
|
21
|
+
const key = isCryptoKey(privateKey)
|
|
22
|
+
? privateKey
|
|
23
|
+
: await subtle().importKey('jwk', privateKey, ED25519, false, ['sign']);
|
|
24
|
+
return { keyId, sign: async (data) => new Uint8Array(await subtle().sign(ED25519, key, data)) };
|
|
25
|
+
}
|
|
26
|
+
async function ed25519Keyring(keys) {
|
|
27
|
+
const imported = new Map();
|
|
28
|
+
for (const [keyId, key] of Object.entries(keys)) {
|
|
29
|
+
imported.set(keyId, isCryptoKey(key) ? key : await subtle().importKey('jwk', key, ED25519, false, ['verify']));
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
async verify(keyId, data, signature) {
|
|
33
|
+
const key = imported.get(keyId);
|
|
34
|
+
if (!key)
|
|
35
|
+
return false;
|
|
36
|
+
try {
|
|
37
|
+
return await subtle().verify(ED25519, key, signature, data);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function contextDigest(content) {
|
|
46
|
+
const text = (0, version_js_1.canonicalJson)({ format: 'nexus-context-signature', formatVersion: 1, ...content });
|
|
47
|
+
const digest = await subtle().digest('SHA-256', new TextEncoder().encode(text));
|
|
48
|
+
return `sha256:${hexOf(new Uint8Array(digest))}`;
|
|
49
|
+
}
|
|
50
|
+
async function signDigest(signer, digest, at) {
|
|
51
|
+
const signature = await signer.sign(new TextEncoder().encode(digest));
|
|
52
|
+
return {
|
|
53
|
+
keyId: signer.keyId,
|
|
54
|
+
algorithm: 'Ed25519',
|
|
55
|
+
digest,
|
|
56
|
+
signature: toBase64Url(signature),
|
|
57
|
+
signedAt: at.toISOString(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async function verifiedBy(keyring, digest, signatures = []) {
|
|
61
|
+
const data = new TextEncoder().encode(digest);
|
|
62
|
+
for (const signature of signatures) {
|
|
63
|
+
if (signature?.algorithm !== 'Ed25519' || signature.digest !== digest)
|
|
64
|
+
continue;
|
|
65
|
+
let bytes;
|
|
66
|
+
try {
|
|
67
|
+
bytes = fromBase64Url(signature.signature);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (await keyring.verify(signature.keyId, data, bytes))
|
|
73
|
+
return signature;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
async function keyIdOf(publicKey) {
|
|
78
|
+
const digest = await subtle().digest('SHA-256', new TextEncoder().encode(String(publicKey.x)));
|
|
79
|
+
return hexOf(new Uint8Array(digest).subarray(0, 8));
|
|
80
|
+
}
|
|
81
|
+
function hexOf(bytes) {
|
|
82
|
+
let hex = '';
|
|
83
|
+
for (const byte of bytes)
|
|
84
|
+
hex += byte.toString(16).padStart(2, '0');
|
|
85
|
+
return hex;
|
|
86
|
+
}
|
|
87
|
+
function isCryptoKey(value) {
|
|
88
|
+
return typeof value.algorithm === 'object' && Array.isArray(value.usages);
|
|
89
|
+
}
|
|
90
|
+
function toBase64Url(bytes) {
|
|
91
|
+
return (0, base64_js_1.encodeBase64)(bytes).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
92
|
+
}
|
|
93
|
+
function fromBase64Url(text) {
|
|
94
|
+
const padded = text.replace(/-/g, '+').replace(/_/g, '/');
|
|
95
|
+
return (0, base64_js_1.decodeBase64)(padded + '='.repeat((4 - (padded.length % 4)) % 4));
|
|
96
|
+
}
|
|
@@ -42,8 +42,10 @@ export interface DoctorDeployment {
|
|
|
42
42
|
replicas: number;
|
|
43
43
|
/**
|
|
44
44
|
* The stores and shared state it uses, by any name: a checkpointer, the operation store, the
|
|
45
|
-
* server's state, tenant usage, a rate-limit store, a circuit store, a cache. Each is
|
|
46
|
-
* it keeps its state in process memory, where each replica would see only its own
|
|
45
|
+
* server's state, tenant usage, a rate-limit store, a circuit store, a cache, a tracer. Each is
|
|
46
|
+
* reported when it keeps its state in process memory, where each replica would see only its own,
|
|
47
|
+
* and when it lacks an opt-in setting a deployment needs: a Redis operation store without its
|
|
48
|
+
* dispatch index, or a tracer writing to a persistent store without incremental tracing.
|
|
47
49
|
*/
|
|
48
50
|
stores?: Record<string, unknown>;
|
|
49
51
|
}
|
package/dist-cjs/doctor/index.js
CHANGED
|
@@ -93,6 +93,7 @@ async function diagnose(options = {}) {
|
|
|
93
93
|
await run('queue', 'Operation queue and leases', () => queueCheck(options.operations, now(), options.maxQueueAgeMs));
|
|
94
94
|
await run('health', 'Provider health', () => healthCheck(options.ai));
|
|
95
95
|
await run('topology', 'Settings that only work in one process', () => topologyCheck(options.deployment));
|
|
96
|
+
await run('settings', 'Opt-in settings a deployment needs', () => settingsCheck(options.deployment));
|
|
96
97
|
const failures = checks.filter((check) => check.status === 'fail').length;
|
|
97
98
|
const warnings = checks.filter((check) => check.status === 'warn').length;
|
|
98
99
|
return { checks, ok: failures === 0, failures, warnings };
|
|
@@ -274,3 +275,31 @@ function topologyCheck(deployment) {
|
|
|
274
275
|
hint: 'Use the Redis, Postgres, or SQLite adapter for each, or run one replica.',
|
|
275
276
|
};
|
|
276
277
|
}
|
|
278
|
+
function settingsCheck(deployment) {
|
|
279
|
+
const stores = Object.entries(deployment?.stores ?? {});
|
|
280
|
+
if (stores.length === 0)
|
|
281
|
+
return { status: 'skip', detail: 'No stores described.' };
|
|
282
|
+
const findings = [];
|
|
283
|
+
const hints = new Set();
|
|
284
|
+
for (const [name, value] of stores) {
|
|
285
|
+
const kind = kindOf(value);
|
|
286
|
+
if (kind === 'RedisOperationStore' && value.indexed === false) {
|
|
287
|
+
findings.push(`${name} is a RedisOperationStore without index: true, so finding queued work reads every record and a long queue slows every claim`);
|
|
288
|
+
hints.add('Give the Redis operation store index: true once every worker sharing its prefix runs 2.2 or later, then call reindex() once.');
|
|
289
|
+
}
|
|
290
|
+
if (kind === 'Tracer') {
|
|
291
|
+
const tracer = value;
|
|
292
|
+
const store = kindOf(tracer.store);
|
|
293
|
+
if (tracer.incremental === false && store && store !== 'MemoryTraceStore') {
|
|
294
|
+
findings.push(`${name} writes to a ${store} without incremental, so a trace is written when its root finishes and a replica that dies mid-run loses it`);
|
|
295
|
+
hints.add('Create the Tracer with incremental: true, and close what a dead process left with closeAbandonedRuns().');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (findings.length === 0)
|
|
300
|
+
return { status: 'ok', detail: 'No store lacks a setting a deployment needs.' };
|
|
301
|
+
return { status: 'warn', detail: `${findings.join('; ')}.`, hint: [...hints].join(' ') };
|
|
302
|
+
}
|
|
303
|
+
function kindOf(value) {
|
|
304
|
+
return value?.constructor?.name;
|
|
305
|
+
}
|