@tenova/swt3-ai 0.5.3 → 0.5.5
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/README.md +100 -8
- package/dist/adapters/cerebras.d.ts +25 -0
- package/dist/adapters/cerebras.d.ts.map +1 -0
- package/dist/adapters/cerebras.js +79 -0
- package/dist/adapters/cerebras.js.map +1 -0
- package/dist/clearing.d.ts +15 -3
- package/dist/clearing.d.ts.map +1 -1
- package/dist/clearing.js +42 -10
- package/dist/clearing.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +54 -2
- package/dist/config.js.map +1 -1
- package/dist/demo.d.ts.map +1 -1
- package/dist/demo.js +6 -2
- package/dist/demo.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +20 -0
- package/dist/doctor.js.map +1 -1
- package/dist/exporters/evidence.d.ts +59 -0
- package/dist/exporters/evidence.d.ts.map +1 -0
- package/dist/exporters/evidence.js +148 -0
- package/dist/exporters/evidence.js.map +1 -0
- package/dist/index.d.ts +10 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/merkle.d.ts +15 -0
- package/dist/merkle.d.ts.map +1 -1
- package/dist/merkle.js +24 -0
- package/dist/merkle.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +60 -4
- package/dist/schema.js.map +1 -1
- package/dist/sentinel-client.d.ts +90 -0
- package/dist/sentinel-client.d.ts.map +1 -0
- package/dist/sentinel-client.js +179 -0
- package/dist/sentinel-client.js.map +1 -0
- package/dist/signing.d.ts +31 -6
- package/dist/signing.d.ts.map +1 -1
- package/dist/signing.js +105 -12
- package/dist/signing.js.map +1 -1
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -1
- package/dist/wal.d.ts +1 -1
- package/dist/wal.js +1 -1
- package/dist/witness.d.ts +117 -0
- package/dist/witness.d.ts.map +1 -1
- package/dist/witness.js +334 -9
- package/dist/witness.js.map +1 -1
- package/package.json +3 -2
- package/templates/autonomous-systems.yaml +70 -0
- package/templates/content-platform.yaml +68 -0
- package/templates/defense-govcon.yaml +77 -0
- package/templates/fintech-model-risk.yaml +69 -0
- package/templates/github-action.yml +44 -0
- package/templates/healthcare-clinical.yaml +67 -0
- package/templates/insurance-underwriting.yaml +68 -0
- package/templates/microsoft-foundry.yaml +61 -0
- package/templates/telecom-compliance.yaml +72 -0
package/dist/witness.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { randomUUID } from "node:crypto";
|
|
23
23
|
import { sha256Truncated, mintFingerprint, timestampMs } from "./fingerprint.js";
|
|
24
|
-
import { extractPayloads, extractGatekeeperPayload, extractRevocationPayload, REVOCATION_REASONS } from "./clearing.js";
|
|
24
|
+
import { extractPayloads, extractGatekeeperPayload, extractRevocationPayload, extractChainTrustDegradationPayload, REVOCATION_REASONS } from "./clearing.js";
|
|
25
25
|
import { signPayload } from "./signing.js";
|
|
26
26
|
import { WitnessBuffer } from "./buffer.js";
|
|
27
27
|
import { WriteAheadLog } from "./wal.js";
|
|
@@ -34,7 +34,7 @@ import { queryHardware as queryHw, topologyCode as topoCode, queryTPM as queryTp
|
|
|
34
34
|
import { queryEnvironment as queryEnv } from "./environment.js";
|
|
35
35
|
import { TrustRegistry, verifyCredential, signCredential, TRUST_LEVEL_NAMES, } from "./trust.js";
|
|
36
36
|
import { createVercelOnFinish } from "./adapters/vercel-ai.js";
|
|
37
|
-
import { QUANTIZATION_CODES, POLICY_CATEGORIES, BINDING_METHODS, APPROVAL_STATUS, PII_EVENT_TYPES } from "./types.js";
|
|
37
|
+
import { QUANTIZATION_CODES, POLICY_CATEGORIES, BINDING_METHODS, APPROVAL_STATUS, PII_EVENT_TYPES, CONTENT_TYPE_CODES, BASELINE_MODE_CODES } from "./types.js";
|
|
38
38
|
import { loadFullConfig, validatePolicy } from "./config.js";
|
|
39
39
|
import { MerkleAccumulator } from "./merkle.js";
|
|
40
40
|
// ── Chain Density Enforcement ──────────────────────────────────────────
|
|
@@ -194,12 +194,25 @@ export class GatekeeperError extends Error {
|
|
|
194
194
|
this.missingNames = missingNames;
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
|
+
export class ChainTrustError extends Error {
|
|
198
|
+
effectiveTrustLevel;
|
|
199
|
+
minimumRequired;
|
|
200
|
+
constructor(effective, minimum) {
|
|
201
|
+
super(`Chain trust blocked: effective level ${effective} below minimum ${minimum}`);
|
|
202
|
+
this.name = "ChainTrustError";
|
|
203
|
+
this.effectiveTrustLevel = effective;
|
|
204
|
+
this.minimumRequired = minimum;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
197
207
|
export class Witness {
|
|
198
208
|
config;
|
|
199
209
|
buffer;
|
|
200
210
|
handoffWarned = false;
|
|
201
211
|
_strict;
|
|
202
212
|
_gatewayMode;
|
|
213
|
+
_chainTrustLevels = [];
|
|
214
|
+
_onViolation;
|
|
215
|
+
_walPath;
|
|
203
216
|
/** True if the SDK is deferring witnessing to an SWT3 Gateway. */
|
|
204
217
|
get gatewayMode() {
|
|
205
218
|
return this._gatewayMode;
|
|
@@ -239,6 +252,14 @@ export class Witness {
|
|
|
239
252
|
if (loaded.hardware.requireAttestation) {
|
|
240
253
|
witness.witnessHardware();
|
|
241
254
|
}
|
|
255
|
+
if (loaded.hardware.runtimeProfile && witness._lastHwSnapshot) {
|
|
256
|
+
witness._validateRuntimeProfile(loaded.hardware.runtimeProfile, witness._lastHwSnapshot);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (loaded.skillCard && loaded.skillCard.skills.length > 0) {
|
|
260
|
+
witness.witnessSkillManifest(loaded.skillCard.skills, {
|
|
261
|
+
expectedManifestHash: loaded.skillCard.expectedManifestHash,
|
|
262
|
+
});
|
|
242
263
|
}
|
|
243
264
|
if (loaded.densityPolicy) {
|
|
244
265
|
witness._densityPolicy = loaded.densityPolicy;
|
|
@@ -260,15 +281,22 @@ export class Witness {
|
|
|
260
281
|
});
|
|
261
282
|
}
|
|
262
283
|
}
|
|
284
|
+
// Fire-and-forget sentinel auto-detection (patent pending).
|
|
285
|
+
// Non-blocking: <10ms probe. If daemon is running, wrapTool/record
|
|
286
|
+
// will delegate to it. If not, SDK operates standalone as before.
|
|
287
|
+
witness._detectSentinelAsync();
|
|
263
288
|
return witness;
|
|
264
289
|
}
|
|
265
290
|
_configHash;
|
|
266
291
|
_hardwareConfig;
|
|
292
|
+
_lastHwSnapshot;
|
|
267
293
|
_densityPolicy;
|
|
268
294
|
_mcpPolicy;
|
|
269
295
|
_merkleConfig;
|
|
270
296
|
_merkleAccumulator;
|
|
271
297
|
_chainEnforcer;
|
|
298
|
+
_sentinel;
|
|
299
|
+
_sentinelDetecting = false;
|
|
272
300
|
get configHash() {
|
|
273
301
|
return this._configHash;
|
|
274
302
|
}
|
|
@@ -292,11 +320,72 @@ export class Witness {
|
|
|
292
320
|
get chainEnforcer() {
|
|
293
321
|
return this._chainEnforcer;
|
|
294
322
|
}
|
|
323
|
+
/** Connected sentinel daemon client (null if no daemon detected). */
|
|
324
|
+
get sentinel() {
|
|
325
|
+
return this._sentinel;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Connect to a running swt3-sentinel daemon.
|
|
329
|
+
*
|
|
330
|
+
* When connected, chain enforcement, signing, and WAL operations are
|
|
331
|
+
* delegated to the isolated daemon process. If the daemon is not running,
|
|
332
|
+
* returns false and the SDK continues with local enforcement.
|
|
333
|
+
*
|
|
334
|
+
* This method is called automatically by fromConfig() in fire-and-forget
|
|
335
|
+
* mode. You only need to call it explicitly if you want to await the
|
|
336
|
+
* connection or use a custom socket path.
|
|
337
|
+
*/
|
|
338
|
+
async connectSentinel(socketPath) {
|
|
339
|
+
try {
|
|
340
|
+
const { SentinelClient } = await import("./sentinel-client.js");
|
|
341
|
+
const client = await SentinelClient.detect(socketPath);
|
|
342
|
+
if (client) {
|
|
343
|
+
this._sentinel = client;
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// Sentinel not available -- silent fallback
|
|
349
|
+
}
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Fire-and-forget sentinel detection. Called from fromConfig().
|
|
354
|
+
* Non-blocking: takes <10ms, resolves in background.
|
|
355
|
+
*/
|
|
356
|
+
_detectSentinelAsync() {
|
|
357
|
+
if (this._sentinelDetecting)
|
|
358
|
+
return;
|
|
359
|
+
this._sentinelDetecting = true;
|
|
360
|
+
this.connectSentinel().finally(() => {
|
|
361
|
+
this._sentinelDetecting = false;
|
|
362
|
+
});
|
|
363
|
+
}
|
|
295
364
|
/** Record token usage against the chain enforcer's session budget. */
|
|
296
365
|
recordSessionTokens(count) {
|
|
297
366
|
if (this._chainEnforcer) {
|
|
298
367
|
this._chainEnforcer.recordTokens(count);
|
|
299
368
|
}
|
|
369
|
+
// Mirror to sentinel for cross-process shared budget
|
|
370
|
+
if (this._sentinel?.connected) {
|
|
371
|
+
this._sentinel.recordTokens(count).catch(() => { });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/** Set or replace the violation callback at runtime. */
|
|
375
|
+
set onViolation(cb) {
|
|
376
|
+
this._onViolation = cb;
|
|
377
|
+
}
|
|
378
|
+
_fireViolation(violation) {
|
|
379
|
+
if (this._onViolation) {
|
|
380
|
+
try {
|
|
381
|
+
this._onViolation(violation);
|
|
382
|
+
}
|
|
383
|
+
catch (e) {
|
|
384
|
+
if (typeof process !== "undefined" && process.env.SWT3_DEBUG) {
|
|
385
|
+
console.error("SWT3: onViolation callback threw:", e);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
300
389
|
}
|
|
301
390
|
_recordChainViolation(violation) {
|
|
302
391
|
const record = {
|
|
@@ -315,6 +404,7 @@ export class Witness {
|
|
|
315
404
|
toolCallId: `chain-${violation.timestamp}`,
|
|
316
405
|
};
|
|
317
406
|
this.record(record);
|
|
407
|
+
this._fireViolation(violation);
|
|
318
408
|
}
|
|
319
409
|
_configureTrustMesh(mesh) {
|
|
320
410
|
const registry = this.trustRegistry;
|
|
@@ -381,9 +471,12 @@ export class Witness {
|
|
|
381
471
|
legalBasis: options.legalBasis,
|
|
382
472
|
purposeClass: options.purposeClass,
|
|
383
473
|
tokenBudget: options.tokenBudget,
|
|
474
|
+
chainMinTrustLevel: options.chainMinTrustLevel,
|
|
384
475
|
onFlush: options.onFlush,
|
|
385
476
|
};
|
|
386
477
|
this._strict = options.strict ?? false;
|
|
478
|
+
this._onViolation = options.onViolation;
|
|
479
|
+
this._walPath = options.walPath;
|
|
387
480
|
// WAL: crash-resilient buffer persistence + replay protection (patent pending)
|
|
388
481
|
let wal;
|
|
389
482
|
if (options.walPath) {
|
|
@@ -457,7 +550,7 @@ export class Witness {
|
|
|
457
550
|
const policyHash = this.config.policyVersion
|
|
458
551
|
? sha256Truncated(this.config.policyVersion, 12)
|
|
459
552
|
: undefined;
|
|
460
|
-
const payload = extractGatekeeperPayload(this.config.tenantId, required, active, gatePassed, this.config.clearingLevel, this.config.agentId, this.config.signingKey, this.config.signingKeyId, this.config.signingKeyVersion, this.config.cycleId, policyHash, this.config.jurisdiction, this.config.legalBasis, this.config.purposeClass);
|
|
553
|
+
const payload = extractGatekeeperPayload(this.config.tenantId, required, active, gatePassed, this.config.clearingLevel, this.config.agentId, this.config.signingKey, this.config.signingKeyId, this.config.signingKeyVersion, this.config.cycleId, policyHash, this.config.jurisdiction, this.config.legalBasis, this.config.purposeClass, this.config.signingAlgorithm);
|
|
461
554
|
this.buffer.enqueueMany([payload]);
|
|
462
555
|
if (!gatePassed) {
|
|
463
556
|
throw new GatekeeperError(required, active);
|
|
@@ -480,16 +573,23 @@ export class Witness {
|
|
|
480
573
|
const name = toolName ?? fn.name ?? "anonymous";
|
|
481
574
|
const self = this;
|
|
482
575
|
const wrapper = function (...args) {
|
|
483
|
-
// Chain density enforcement -- BEFORE execution
|
|
576
|
+
// Chain density enforcement -- BEFORE execution.
|
|
577
|
+
// Local enforcer runs synchronously (fast path).
|
|
578
|
+
// Sentinel (if connected) mirrors the check for cross-process shared state.
|
|
484
579
|
if (self._chainEnforcer) {
|
|
485
580
|
const violation = self._chainEnforcer.check(name);
|
|
486
581
|
if (violation) {
|
|
487
582
|
if (violation.action === "blocked") {
|
|
583
|
+
self._fireViolation(violation);
|
|
488
584
|
throw new PolicyViolationError(violation);
|
|
489
585
|
}
|
|
490
586
|
self._recordChainViolation(violation);
|
|
491
587
|
}
|
|
492
588
|
}
|
|
589
|
+
// Mirror to sentinel for cross-process state (fire-and-forget).
|
|
590
|
+
if (self._sentinel?.connected) {
|
|
591
|
+
self._sentinel.check(name).catch(() => { });
|
|
592
|
+
}
|
|
493
593
|
const callId = randomUUID().replace(/-/g, "").slice(0, 12);
|
|
494
594
|
const start = performance.now();
|
|
495
595
|
let succeeded = true;
|
|
@@ -661,7 +761,9 @@ export class Witness {
|
|
|
661
761
|
payload.policy_version_hash = sha256Truncated(this.config.policyVersion, 12);
|
|
662
762
|
}
|
|
663
763
|
if (this.config.signingKey) {
|
|
664
|
-
|
|
764
|
+
const algo = this.config.signingAlgorithm ?? "hmac-sha256";
|
|
765
|
+
payload.payload_signature = signPayload(this.config.signingKey, fp, this.config.agentId, algo);
|
|
766
|
+
payload.signing_algorithm = algo;
|
|
665
767
|
if (this.config.signingKeyId)
|
|
666
768
|
payload.signing_key_id = this.config.signingKeyId;
|
|
667
769
|
if (this.config.signingKeyVersion !== undefined)
|
|
@@ -716,7 +818,9 @@ export class Witness {
|
|
|
716
818
|
payload.policy_version_hash = sha256Truncated(this.config.policyVersion, 12);
|
|
717
819
|
}
|
|
718
820
|
if (this.config.signingKey) {
|
|
719
|
-
|
|
821
|
+
const algo = this.config.signingAlgorithm ?? "hmac-sha256";
|
|
822
|
+
payload.payload_signature = signPayload(this.config.signingKey, fp, this.config.agentId, algo);
|
|
823
|
+
payload.signing_algorithm = algo;
|
|
720
824
|
if (this.config.signingKeyId)
|
|
721
825
|
payload.signing_key_id = this.config.signingKeyId;
|
|
722
826
|
if (this.config.signingKeyVersion !== undefined)
|
|
@@ -1078,13 +1182,37 @@ export class Witness {
|
|
|
1078
1182
|
if (policyHash)
|
|
1079
1183
|
payload.policy_version_hash = policyHash;
|
|
1080
1184
|
if (this.config.signingKey) {
|
|
1081
|
-
|
|
1185
|
+
const alg = this.config.signingAlgorithm ?? "hmac-sha256";
|
|
1186
|
+
payload.payload_signature = signPayload(this.config.signingKey, payload.anchor_fingerprint, this.config.agentId, alg);
|
|
1187
|
+
payload.signing_algorithm = alg;
|
|
1082
1188
|
if (this.config.signingKeyId)
|
|
1083
1189
|
payload.signing_key_id = this.config.signingKeyId;
|
|
1084
1190
|
if (this.config.signingKeyVersion !== undefined)
|
|
1085
1191
|
payload.signing_key_version = this.config.signingKeyVersion;
|
|
1086
1192
|
}
|
|
1087
1193
|
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Validate hardware snapshot against a runtime profile. Logs warnings
|
|
1196
|
+
* on mismatch but never blocks -- we are a witness, not enforcement.
|
|
1197
|
+
*/
|
|
1198
|
+
_validateRuntimeProfile(profile, snapshot) {
|
|
1199
|
+
const warn = (msg) => console.warn(`[swt3] runtime profile: ${msg}`);
|
|
1200
|
+
if (profile.expectedTopology && snapshot.topology !== profile.expectedTopology) {
|
|
1201
|
+
warn(`expected topology "${profile.expectedTopology}", got "${snapshot.topology}"`);
|
|
1202
|
+
}
|
|
1203
|
+
if (profile.minGpuCount != null && snapshot.gpus.length < profile.minGpuCount) {
|
|
1204
|
+
warn(`expected min_gpu_count=${profile.minGpuCount}, got ${snapshot.gpus.length}`);
|
|
1205
|
+
}
|
|
1206
|
+
if (profile.minMemoryMb != null && snapshot.totalMemoryMb < profile.minMemoryMb) {
|
|
1207
|
+
warn(`expected min_memory_mb=${profile.minMemoryMb}, got ${snapshot.totalMemoryMb}`);
|
|
1208
|
+
}
|
|
1209
|
+
if (profile.expectedAccelerator) {
|
|
1210
|
+
const match = snapshot.gpus.some((g) => g.name.toUpperCase().includes(profile.expectedAccelerator.toUpperCase()));
|
|
1211
|
+
if (!match) {
|
|
1212
|
+
warn(`expected accelerator containing "${profile.expectedAccelerator}", none found`);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1088
1216
|
/**
|
|
1089
1217
|
* Revoke a previously-issued witness anchor (AI-REV.1).
|
|
1090
1218
|
*
|
|
@@ -1111,6 +1239,7 @@ export class Witness {
|
|
|
1111
1239
|
*/
|
|
1112
1240
|
witnessHardware(options) {
|
|
1113
1241
|
const snapshot = options?.snapshot ?? queryHw();
|
|
1242
|
+
this._lastHwSnapshot = snapshot;
|
|
1114
1243
|
const gpuCount = snapshot.gpus.length;
|
|
1115
1244
|
let allHealthy = gpuCount > 0;
|
|
1116
1245
|
if (options?.expectedTopology && snapshot.topology !== options.expectedTopology) {
|
|
@@ -1315,6 +1444,110 @@ export class Witness {
|
|
|
1315
1444
|
this.buffer.enqueueMany([payload]);
|
|
1316
1445
|
return payload;
|
|
1317
1446
|
}
|
|
1447
|
+
// ── Content Provenance (AI-MARK.1) ────────────────────────────
|
|
1448
|
+
/**
|
|
1449
|
+
* Witness content provenance marking (AI-MARK.1).
|
|
1450
|
+
*
|
|
1451
|
+
* Records when AI-generated content is labelled for machine
|
|
1452
|
+
* detectability per EU AI Act Art. 50(2) and GPAI Code of Practice.
|
|
1453
|
+
*
|
|
1454
|
+
* @param options.contentCount - Number of content items marked.
|
|
1455
|
+
* @param options.contentType - Content type key (text, image, audio, video, multimodal, code, structured_data).
|
|
1456
|
+
* @param options.markingMethod - Marking method (c2pa, watermark, metadata_tag, steganographic, manifest).
|
|
1457
|
+
* @param options.hasMetadata - True if C2PA/watermark metadata was attached.
|
|
1458
|
+
* @param options.content - Raw content string (auto-hashed to contentHash).
|
|
1459
|
+
* @param options.contentHash - Pre-computed SHA-256 of content.
|
|
1460
|
+
* @param options.manifestHash - SHA-256 of C2PA manifest.
|
|
1461
|
+
* @param options.standard - Standard identifier (e.g., "C2PA-1.4", "IPTC", "XMP").
|
|
1462
|
+
*/
|
|
1463
|
+
witnessContentMark(options) {
|
|
1464
|
+
const fa = options.contentCount;
|
|
1465
|
+
const fb = options.hasMetadata ? 1 : 0;
|
|
1466
|
+
const fc = CONTENT_TYPE_CODES[options.contentType] ?? 0;
|
|
1467
|
+
const [ts, epoch] = timestampMs();
|
|
1468
|
+
const fp = mintFingerprint(this.config.tenantId, "AI-MARK.1", fa, fb, fc, ts);
|
|
1469
|
+
const contentHash = options.contentHash
|
|
1470
|
+
?? (options.content ? sha256Truncated(options.content) : undefined);
|
|
1471
|
+
const payload = {
|
|
1472
|
+
procedure_id: "AI-MARK.1",
|
|
1473
|
+
factor_a: fa, factor_b: fb, factor_c: fc,
|
|
1474
|
+
clearing_level: this.config.clearingLevel,
|
|
1475
|
+
anchor_fingerprint: fp, anchor_epoch: epoch, fingerprint_timestamp_ms: ts,
|
|
1476
|
+
};
|
|
1477
|
+
if (this.config.clearingLevel <= 1) {
|
|
1478
|
+
payload.ai_model_id = `mark-${options.contentType}`;
|
|
1479
|
+
const ctx = {
|
|
1480
|
+
provider: "content-provenance",
|
|
1481
|
+
content_type: options.contentType,
|
|
1482
|
+
marking_method: options.markingMethod,
|
|
1483
|
+
};
|
|
1484
|
+
if (contentHash)
|
|
1485
|
+
ctx.content_hash = contentHash;
|
|
1486
|
+
if (options.manifestHash)
|
|
1487
|
+
ctx.manifest_hash = options.manifestHash;
|
|
1488
|
+
if (options.standard)
|
|
1489
|
+
ctx.standard = options.standard;
|
|
1490
|
+
payload.ai_context = ctx;
|
|
1491
|
+
}
|
|
1492
|
+
const policyHash = this.config.policyVersion
|
|
1493
|
+
? sha256Truncated(this.config.policyVersion, 12) : undefined;
|
|
1494
|
+
this._applyOperationalMetadata(payload, policyHash);
|
|
1495
|
+
this.buffer.enqueueMany([payload]);
|
|
1496
|
+
return payload;
|
|
1497
|
+
}
|
|
1498
|
+
// ── Agent Behavioral Baseline (AI-BASE.1) ───────────────────
|
|
1499
|
+
/**
|
|
1500
|
+
* Witness an agent behavioral baseline (AI-BASE.1).
|
|
1501
|
+
*
|
|
1502
|
+
* Records the establishment or monitoring of an agent's behavior
|
|
1503
|
+
* envelope and detects drift from baseline.
|
|
1504
|
+
*
|
|
1505
|
+
* @param options.dimensions - Number of behavioral dimensions measured.
|
|
1506
|
+
* @param options.withinEnvelope - True if behavior is within baseline.
|
|
1507
|
+
* @param options.mode - Baseline mode (establishing, monitoring, drift_detected, baseline_reset).
|
|
1508
|
+
* @param options.driftScore - Normalized distance from baseline center (0.0-1.0).
|
|
1509
|
+
* @param options.baselineHash - SHA-256 of the baseline vector.
|
|
1510
|
+
* @param options.currentHash - SHA-256 of current observation vector.
|
|
1511
|
+
* @param options.driftThreshold - Threshold above which drift is flagged (default 0.5).
|
|
1512
|
+
* @param options.baselineWindowHours - Hours of data the baseline covers.
|
|
1513
|
+
*/
|
|
1514
|
+
witnessAgentBaseline(options) {
|
|
1515
|
+
const fa = options.dimensions;
|
|
1516
|
+
const fb = options.withinEnvelope ? 1 : 0;
|
|
1517
|
+
const fc = BASELINE_MODE_CODES[options.mode] ?? 0;
|
|
1518
|
+
const [ts, epoch] = timestampMs();
|
|
1519
|
+
const fp = mintFingerprint(this.config.tenantId, "AI-BASE.1", fa, fb, fc, ts);
|
|
1520
|
+
const agentIdHash = this.config.agentId
|
|
1521
|
+
? sha256Truncated(this.config.agentId)
|
|
1522
|
+
: undefined;
|
|
1523
|
+
const payload = {
|
|
1524
|
+
procedure_id: "AI-BASE.1",
|
|
1525
|
+
factor_a: fa, factor_b: fb, factor_c: fc,
|
|
1526
|
+
clearing_level: this.config.clearingLevel,
|
|
1527
|
+
anchor_fingerprint: fp, anchor_epoch: epoch, fingerprint_timestamp_ms: ts,
|
|
1528
|
+
};
|
|
1529
|
+
if (this.config.clearingLevel <= 1) {
|
|
1530
|
+
payload.ai_model_id = `baseline-${options.mode}`;
|
|
1531
|
+
const ctx = {
|
|
1532
|
+
provider: "agent-baseline",
|
|
1533
|
+
dimensions: options.dimensions,
|
|
1534
|
+
drift_score: options.driftScore,
|
|
1535
|
+
baseline_hash: options.baselineHash,
|
|
1536
|
+
current_hash: options.currentHash,
|
|
1537
|
+
drift_threshold: options.driftThreshold ?? 0.5,
|
|
1538
|
+
};
|
|
1539
|
+
if (options.baselineWindowHours != null)
|
|
1540
|
+
ctx.baseline_window_hours = options.baselineWindowHours;
|
|
1541
|
+
if (agentIdHash)
|
|
1542
|
+
ctx.agent_id_hash = agentIdHash;
|
|
1543
|
+
payload.ai_context = ctx;
|
|
1544
|
+
}
|
|
1545
|
+
const policyHash = this.config.policyVersion
|
|
1546
|
+
? sha256Truncated(this.config.policyVersion, 12) : undefined;
|
|
1547
|
+
this._applyOperationalMetadata(payload, policyHash);
|
|
1548
|
+
this.buffer.enqueueMany([payload]);
|
|
1549
|
+
return payload;
|
|
1550
|
+
}
|
|
1318
1551
|
// ── Chain, Violation, Charter, Registry, Reviewer, Safe State ───
|
|
1319
1552
|
/**
|
|
1320
1553
|
* Witness a multi-agent chain handoff (AI-CHAIN.1).
|
|
@@ -1342,6 +1575,78 @@ export class Witness {
|
|
|
1342
1575
|
this.buffer.enqueueMany([payload]);
|
|
1343
1576
|
return payload;
|
|
1344
1577
|
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Witness a trust-level-aware chain handoff (AI-CHAIN.1 + optional AI-CHAIN.2).
|
|
1580
|
+
*
|
|
1581
|
+
* Tracks the effective trust level across all agents in the chain.
|
|
1582
|
+
* If chainMinTrustLevel is configured and strict mode is active,
|
|
1583
|
+
* throws ChainTrustError when the effective level drops below the minimum.
|
|
1584
|
+
*
|
|
1585
|
+
* Auto-mints AI-CHAIN.2 (trust degradation) when the effective trust
|
|
1586
|
+
* level drops compared to the previous handoff.
|
|
1587
|
+
*/
|
|
1588
|
+
witnessChainTrustHandoff(targetAgentId, targetTrustLevel, options) {
|
|
1589
|
+
const accepted = options?.accepted ?? true;
|
|
1590
|
+
const prevEffective = this._chainTrustLevels.length > 0
|
|
1591
|
+
? Math.min(...this._chainTrustLevels)
|
|
1592
|
+
: targetTrustLevel;
|
|
1593
|
+
this._chainTrustLevels.push(targetTrustLevel);
|
|
1594
|
+
const effectiveTrustLevel = Math.min(...this._chainTrustLevels);
|
|
1595
|
+
// Enforce minimum if configured
|
|
1596
|
+
const minLevel = this.config.chainMinTrustLevel;
|
|
1597
|
+
if (minLevel !== undefined && effectiveTrustLevel < minLevel && this._strict) {
|
|
1598
|
+
throw new ChainTrustError(effectiveTrustLevel, minLevel);
|
|
1599
|
+
}
|
|
1600
|
+
const [ts, epoch] = timestampMs();
|
|
1601
|
+
const fa = this._chainTrustLevels.length;
|
|
1602
|
+
const fb = targetTrustLevel;
|
|
1603
|
+
const fc = effectiveTrustLevel;
|
|
1604
|
+
const fp = mintFingerprint(this.config.tenantId, "AI-CHAIN.1", fa, fb, fc, ts);
|
|
1605
|
+
const payload = {
|
|
1606
|
+
procedure_id: "AI-CHAIN.1", factor_a: fa, factor_b: fb, factor_c: fc,
|
|
1607
|
+
clearing_level: this.config.clearingLevel,
|
|
1608
|
+
anchor_fingerprint: fp, anchor_epoch: epoch, fingerprint_timestamp_ms: ts,
|
|
1609
|
+
};
|
|
1610
|
+
if (this.config.clearingLevel <= 1) {
|
|
1611
|
+
payload.ai_model_id = targetAgentId;
|
|
1612
|
+
payload.ai_context = {
|
|
1613
|
+
provider: "chain-trust",
|
|
1614
|
+
target_agent: targetAgentId,
|
|
1615
|
+
target_trust_level: targetTrustLevel,
|
|
1616
|
+
effective_trust_level: effectiveTrustLevel,
|
|
1617
|
+
chain_depth: this._chainTrustLevels.length,
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
const cycleId = options?.cycleId ?? this.config.cycleId;
|
|
1621
|
+
const policyHash = this.config.policyVersion ? sha256Truncated(this.config.policyVersion, 12) : undefined;
|
|
1622
|
+
this._applyOperationalMetadata(payload, policyHash);
|
|
1623
|
+
if (cycleId)
|
|
1624
|
+
payload.cycle_id = cycleId;
|
|
1625
|
+
const payloads = [payload];
|
|
1626
|
+
// Auto-mint AI-CHAIN.2 if trust degraded
|
|
1627
|
+
if (this._chainTrustLevels.length > 1 && effectiveTrustLevel < prevEffective) {
|
|
1628
|
+
const degradation = extractChainTrustDegradationPayload(this.config.tenantId, prevEffective, effectiveTrustLevel, this.config.clearingLevel, this.config.agentId, this.config.signingKey, this.config.signingKeyId, this.config.signingKeyVersion, cycleId, policyHash, this.config.signingAlgorithm);
|
|
1629
|
+
if (this.config.clearingLevel <= 1) {
|
|
1630
|
+
degradation.ai_context = {
|
|
1631
|
+
provider: "chain-trust-degradation",
|
|
1632
|
+
previous_effective: prevEffective,
|
|
1633
|
+
new_effective: effectiveTrustLevel,
|
|
1634
|
+
target_agent: targetAgentId,
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
payloads.push(degradation);
|
|
1638
|
+
}
|
|
1639
|
+
this.buffer.enqueueMany(payloads);
|
|
1640
|
+
return payload;
|
|
1641
|
+
}
|
|
1642
|
+
/** The effective (minimum) trust level across all chain handoffs. Returns 4 if no handoffs yet. */
|
|
1643
|
+
get chainEffectiveTrustLevel() {
|
|
1644
|
+
return this._chainTrustLevels.length === 0 ? 4 : Math.min(...this._chainTrustLevels);
|
|
1645
|
+
}
|
|
1646
|
+
/** The trust levels recorded at each chain handoff. */
|
|
1647
|
+
get chainTrustLevels() {
|
|
1648
|
+
return this._chainTrustLevels;
|
|
1649
|
+
}
|
|
1345
1650
|
/**
|
|
1346
1651
|
* Witness a policy violation (AI-VIO.1).
|
|
1347
1652
|
*/
|
|
@@ -1684,7 +1989,7 @@ export class Witness {
|
|
|
1684
1989
|
const policyHash = this.config.policyVersion
|
|
1685
1990
|
? sha256Truncated(this.config.policyVersion, 12)
|
|
1686
1991
|
: undefined;
|
|
1687
|
-
const payload = extractRevocationPayload(this.config.tenantId, fingerprint.trim(), reason, this.config.clearingLevel, this.config.agentId, this.config.signingKey, this.config.signingKeyId, this.config.signingKeyVersion, this.config.cycleId, policyHash, this.config.jurisdiction, this.config.legalBasis, this.config.purposeClass);
|
|
1992
|
+
const payload = extractRevocationPayload(this.config.tenantId, fingerprint.trim(), reason, this.config.clearingLevel, this.config.agentId, this.config.signingKey, this.config.signingKeyId, this.config.signingKeyVersion, this.config.cycleId, policyHash, this.config.jurisdiction, this.config.legalBasis, this.config.purposeClass, this.config.signingAlgorithm);
|
|
1688
1993
|
this.buffer.enqueueMany([payload]);
|
|
1689
1994
|
return payload.anchor_fingerprint;
|
|
1690
1995
|
}
|
|
@@ -1745,7 +2050,7 @@ export class Witness {
|
|
|
1745
2050
|
const policyHash = this.config.policyVersion
|
|
1746
2051
|
? sha256Truncated(this.config.policyVersion, 12)
|
|
1747
2052
|
: undefined;
|
|
1748
|
-
const payloads = extractPayloads(inference, this.config.tenantId, this.config.clearingLevel, this.config.latencyThresholdMs, this.config.guardrailsRequired, this.config.procedures, this.config.agentId, this.config.signingKey, this.config.signingKeyId, this.config.signingKeyVersion, this.config.cycleId, policyHash, this.config.jurisdiction, this.config.legalBasis, this.config.purposeClass, authorizationId);
|
|
2053
|
+
const payloads = extractPayloads(inference, this.config.tenantId, this.config.clearingLevel, this.config.latencyThresholdMs, this.config.guardrailsRequired, this.config.procedures, this.config.agentId, this.config.signingKey, this.config.signingKeyId, this.config.signingKeyVersion, this.config.cycleId, policyHash, this.config.jurisdiction, this.config.legalBasis, this.config.purposeClass, authorizationId, this.config.signingAlgorithm);
|
|
1749
2054
|
// Factor handoff: write full (uncleared) data to custody destination
|
|
1750
2055
|
// BEFORE enqueuing the cleared payload for transmission.
|
|
1751
2056
|
// If this fails, we do NOT proceed.
|
|
@@ -1773,6 +2078,26 @@ export class Witness {
|
|
|
1773
2078
|
vercelOnFinish(options) {
|
|
1774
2079
|
return createVercelOnFinish(this, options);
|
|
1775
2080
|
}
|
|
2081
|
+
/**
|
|
2082
|
+
* Export a self-contained evidence bundle from the local WAL.
|
|
2083
|
+
*
|
|
2084
|
+
* Returns an EvidenceExporter pre-configured with the witness's
|
|
2085
|
+
* tenant, agent, clearing level, and credential state.
|
|
2086
|
+
*/
|
|
2087
|
+
exportEvidence() {
|
|
2088
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
2089
|
+
const { EvidenceExporter } = require("./exporters/evidence.js");
|
|
2090
|
+
return new EvidenceExporter({
|
|
2091
|
+
walDir: this._walPath,
|
|
2092
|
+
tenantId: this.config.tenantId,
|
|
2093
|
+
agentId: this.config.agentId,
|
|
2094
|
+
clearingLevel: this.config.clearingLevel,
|
|
2095
|
+
apiKey: this.config.apiKey,
|
|
2096
|
+
signingKey: this.config.signingKey,
|
|
2097
|
+
hasHardwareAttestation: !!this._hardwareConfig?.requireAttestation,
|
|
2098
|
+
merkleRoots: this._merkleAccumulator?.roots,
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
1776
2101
|
/** Force-flush all buffered payloads. */
|
|
1777
2102
|
async flush() {
|
|
1778
2103
|
return this.buffer.flush();
|