@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.
Files changed (61) hide show
  1. package/README.md +100 -8
  2. package/dist/adapters/cerebras.d.ts +25 -0
  3. package/dist/adapters/cerebras.d.ts.map +1 -0
  4. package/dist/adapters/cerebras.js +79 -0
  5. package/dist/adapters/cerebras.js.map +1 -0
  6. package/dist/clearing.d.ts +15 -3
  7. package/dist/clearing.d.ts.map +1 -1
  8. package/dist/clearing.js +42 -10
  9. package/dist/clearing.js.map +1 -1
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +54 -2
  12. package/dist/config.js.map +1 -1
  13. package/dist/demo.d.ts.map +1 -1
  14. package/dist/demo.js +6 -2
  15. package/dist/demo.js.map +1 -1
  16. package/dist/doctor.d.ts.map +1 -1
  17. package/dist/doctor.js +20 -0
  18. package/dist/doctor.js.map +1 -1
  19. package/dist/exporters/evidence.d.ts +59 -0
  20. package/dist/exporters/evidence.d.ts.map +1 -0
  21. package/dist/exporters/evidence.js +148 -0
  22. package/dist/exporters/evidence.js.map +1 -0
  23. package/dist/index.d.ts +10 -5
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +6 -4
  26. package/dist/index.js.map +1 -1
  27. package/dist/merkle.d.ts +15 -0
  28. package/dist/merkle.d.ts.map +1 -1
  29. package/dist/merkle.js +24 -0
  30. package/dist/merkle.js.map +1 -1
  31. package/dist/schema.d.ts.map +1 -1
  32. package/dist/schema.js +60 -4
  33. package/dist/schema.js.map +1 -1
  34. package/dist/sentinel-client.d.ts +90 -0
  35. package/dist/sentinel-client.d.ts.map +1 -0
  36. package/dist/sentinel-client.js +179 -0
  37. package/dist/sentinel-client.js.map +1 -0
  38. package/dist/signing.d.ts +31 -6
  39. package/dist/signing.d.ts.map +1 -1
  40. package/dist/signing.js +105 -12
  41. package/dist/signing.js.map +1 -1
  42. package/dist/types.d.ts +25 -0
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/types.js +15 -0
  45. package/dist/types.js.map +1 -1
  46. package/dist/wal.d.ts +1 -1
  47. package/dist/wal.js +1 -1
  48. package/dist/witness.d.ts +117 -0
  49. package/dist/witness.d.ts.map +1 -1
  50. package/dist/witness.js +334 -9
  51. package/dist/witness.js.map +1 -1
  52. package/package.json +3 -2
  53. package/templates/autonomous-systems.yaml +70 -0
  54. package/templates/content-platform.yaml +68 -0
  55. package/templates/defense-govcon.yaml +77 -0
  56. package/templates/fintech-model-risk.yaml +69 -0
  57. package/templates/github-action.yml +44 -0
  58. package/templates/healthcare-clinical.yaml +67 -0
  59. package/templates/insurance-underwriting.yaml +68 -0
  60. package/templates/microsoft-foundry.yaml +61 -0
  61. 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
- payload.payload_signature = signPayload(this.config.signingKey, fp, this.config.agentId);
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
- payload.payload_signature = signPayload(this.config.signingKey, fp, this.config.agentId);
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
- payload.payload_signature = signPayload(this.config.signingKey, payload.anchor_fingerprint, this.config.agentId);
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();