@nexart/ai-execution 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +100 -330
  2. package/dist/index.cjs +607 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +56 -0
  5. package/dist/index.d.ts +55 -11
  6. package/dist/index.mjs +550 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/providers/anthropic.cjs +258 -0
  9. package/dist/providers/anthropic.cjs.map +1 -0
  10. package/dist/providers/anthropic.d.cts +24 -0
  11. package/dist/providers/anthropic.d.ts +7 -5
  12. package/dist/providers/anthropic.mjs +221 -0
  13. package/dist/providers/anthropic.mjs.map +1 -0
  14. package/dist/providers/openai.cjs +259 -0
  15. package/dist/providers/openai.cjs.map +1 -0
  16. package/dist/providers/openai.d.cts +24 -0
  17. package/dist/providers/openai.d.ts +7 -5
  18. package/dist/providers/openai.mjs +222 -0
  19. package/dist/providers/openai.mjs.map +1 -0
  20. package/dist/providers/wrap.cjs +221 -0
  21. package/dist/providers/wrap.cjs.map +1 -0
  22. package/dist/providers/wrap.d.cts +9 -0
  23. package/dist/providers/wrap.d.ts +5 -3
  24. package/dist/providers/wrap.mjs +186 -0
  25. package/dist/providers/wrap.mjs.map +1 -0
  26. package/dist/{types.d.ts → types-DF29BsH5.d.cts} +18 -16
  27. package/dist/types-DF29BsH5.d.ts +155 -0
  28. package/package.json +18 -9
  29. package/dist/__tests__/fixtures.test.d.ts +0 -2
  30. package/dist/__tests__/fixtures.test.d.ts.map +0 -1
  31. package/dist/__tests__/fixtures.test.js +0 -37
  32. package/dist/__tests__/fixtures.test.js.map +0 -1
  33. package/dist/__tests__/v020.test.d.ts +0 -2
  34. package/dist/__tests__/v020.test.d.ts.map +0 -1
  35. package/dist/__tests__/v020.test.js +0 -408
  36. package/dist/__tests__/v020.test.js.map +0 -1
  37. package/dist/__tests__/vectors.test.d.ts +0 -2
  38. package/dist/__tests__/vectors.test.d.ts.map +0 -1
  39. package/dist/__tests__/vectors.test.js +0 -261
  40. package/dist/__tests__/vectors.test.js.map +0 -1
  41. package/dist/archive.d.ts +0 -4
  42. package/dist/archive.d.ts.map +0 -1
  43. package/dist/archive.js +0 -28
  44. package/dist/archive.js.map +0 -1
  45. package/dist/attest.d.ts +0 -3
  46. package/dist/attest.d.ts.map +0 -1
  47. package/dist/attest.js +0 -42
  48. package/dist/attest.js.map +0 -1
  49. package/dist/canonicalJson.d.ts +0 -2
  50. package/dist/canonicalJson.d.ts.map +0 -1
  51. package/dist/canonicalJson.js +0 -38
  52. package/dist/canonicalJson.js.map +0 -1
  53. package/dist/cer.d.ts +0 -7
  54. package/dist/cer.d.ts.map +0 -1
  55. package/dist/cer.js +0 -61
  56. package/dist/cer.js.map +0 -1
  57. package/dist/certify.d.ts +0 -3
  58. package/dist/certify.d.ts.map +0 -1
  59. package/dist/certify.js +0 -27
  60. package/dist/certify.js.map +0 -1
  61. package/dist/errors.d.ts +0 -10
  62. package/dist/errors.d.ts.map +0 -1
  63. package/dist/errors.js +0 -19
  64. package/dist/errors.js.map +0 -1
  65. package/dist/hash.d.ts +0 -6
  66. package/dist/hash.d.ts.map +0 -1
  67. package/dist/hash.js +0 -32
  68. package/dist/hash.js.map +0 -1
  69. package/dist/index.d.ts.map +0 -1
  70. package/dist/index.js +0 -11
  71. package/dist/index.js.map +0 -1
  72. package/dist/providers/anthropic.d.ts.map +0 -1
  73. package/dist/providers/anthropic.js +0 -61
  74. package/dist/providers/anthropic.js.map +0 -1
  75. package/dist/providers/openai.d.ts.map +0 -1
  76. package/dist/providers/openai.js +0 -62
  77. package/dist/providers/openai.js.map +0 -1
  78. package/dist/providers/wrap.d.ts.map +0 -1
  79. package/dist/providers/wrap.js +0 -28
  80. package/dist/providers/wrap.js.map +0 -1
  81. package/dist/run.d.ts +0 -14
  82. package/dist/run.d.ts.map +0 -1
  83. package/dist/run.js +0 -62
  84. package/dist/run.js.map +0 -1
  85. package/dist/snapshot.d.ts +0 -4
  86. package/dist/snapshot.d.ts.map +0 -1
  87. package/dist/snapshot.js +0 -113
  88. package/dist/snapshot.js.map +0 -1
  89. package/dist/types.d.ts.map +0 -1
  90. package/dist/types.js +0 -2
  91. package/dist/types.js.map +0 -1
@@ -0,0 +1,155 @@
1
+ interface AiExecutionParameters {
2
+ temperature: number;
3
+ maxTokens: number;
4
+ topP: number | null;
5
+ seed: number | null;
6
+ }
7
+ interface AiExecutionSnapshotV1 {
8
+ type: 'ai.execution.v1';
9
+ protocolVersion: '1.2.0';
10
+ executionSurface: 'ai';
11
+ executionId: string;
12
+ timestamp: string;
13
+ provider: string;
14
+ model: string;
15
+ modelVersion: string | null;
16
+ prompt: string;
17
+ input: string | Record<string, unknown>;
18
+ inputHash: string;
19
+ parameters: AiExecutionParameters;
20
+ output: string | Record<string, unknown>;
21
+ outputHash: string;
22
+ sdkVersion: string | null;
23
+ appId: string | null;
24
+ runId?: string | null;
25
+ stepId?: string | null;
26
+ stepIndex?: number | null;
27
+ workflowId?: string | null;
28
+ conversationId?: string | null;
29
+ prevStepHash?: string | null;
30
+ }
31
+ interface CerMeta {
32
+ source?: string;
33
+ tags?: string[];
34
+ [key: string]: unknown;
35
+ }
36
+ interface CerAiExecutionBundle {
37
+ bundleType: 'cer.ai.execution.v1';
38
+ certificateHash: string;
39
+ createdAt: string;
40
+ version: '0.1';
41
+ snapshot: AiExecutionSnapshotV1;
42
+ meta?: CerMeta;
43
+ }
44
+ interface VerificationResult {
45
+ ok: boolean;
46
+ errors: string[];
47
+ }
48
+ interface CreateSnapshotParams {
49
+ executionId: string;
50
+ timestamp?: string;
51
+ provider: string;
52
+ model: string;
53
+ modelVersion?: string | null;
54
+ prompt: string;
55
+ input: string | Record<string, unknown>;
56
+ parameters: AiExecutionParameters;
57
+ output: string | Record<string, unknown>;
58
+ sdkVersion?: string | null;
59
+ appId?: string | null;
60
+ runId?: string | null;
61
+ stepId?: string | null;
62
+ stepIndex?: number | null;
63
+ workflowId?: string | null;
64
+ conversationId?: string | null;
65
+ prevStepHash?: string | null;
66
+ }
67
+ interface AttestationResult {
68
+ ok: boolean;
69
+ attestationId?: string;
70
+ nodeRuntimeHash?: string;
71
+ certificateHash?: string;
72
+ protocolVersion?: string;
73
+ errors?: string[];
74
+ raw?: unknown;
75
+ }
76
+ interface AttestOptions {
77
+ nodeUrl: string;
78
+ apiKey: string;
79
+ timeoutMs?: number;
80
+ }
81
+ interface CertifyDecisionParams {
82
+ executionId?: string;
83
+ timestamp?: string;
84
+ provider: string;
85
+ model: string;
86
+ modelVersion?: string | null;
87
+ prompt: string;
88
+ input: string | Record<string, unknown>;
89
+ parameters: AiExecutionParameters;
90
+ output: string | Record<string, unknown>;
91
+ sdkVersion?: string | null;
92
+ appId?: string | null;
93
+ meta?: CerMeta;
94
+ runId?: string | null;
95
+ stepId?: string | null;
96
+ stepIndex?: number | null;
97
+ workflowId?: string | null;
98
+ conversationId?: string | null;
99
+ prevStepHash?: string | null;
100
+ }
101
+ interface RunBuilderOptions {
102
+ runId?: string;
103
+ workflowId?: string | null;
104
+ conversationId?: string | null;
105
+ appId?: string | null;
106
+ }
107
+ interface StepParams {
108
+ stepId?: string;
109
+ provider: string;
110
+ model: string;
111
+ modelVersion?: string | null;
112
+ prompt: string;
113
+ input: string | Record<string, unknown>;
114
+ parameters: AiExecutionParameters;
115
+ output: string | Record<string, unknown>;
116
+ timestamp?: string;
117
+ meta?: CerMeta;
118
+ }
119
+ interface RunSummary {
120
+ runId: string;
121
+ workflowId: string | null;
122
+ conversationId: string | null;
123
+ stepCount: number;
124
+ steps: Array<{
125
+ stepIndex: number;
126
+ stepId: string;
127
+ executionId: string;
128
+ certificateHash: string;
129
+ prevStepHash: string | null;
130
+ }>;
131
+ finalStepHash: string | null;
132
+ }
133
+ interface ProviderConfig<TInput = unknown, TOutput = unknown> {
134
+ provider: string;
135
+ callFn: (input: TInput) => Promise<TOutput>;
136
+ extractOutput: (raw: TOutput) => string | Record<string, unknown>;
137
+ extractModelVersion?: (raw: TOutput) => string | null;
138
+ }
139
+ interface WrappedExecutionParams {
140
+ prompt: string;
141
+ input: string | Record<string, unknown>;
142
+ model: string;
143
+ parameters: AiExecutionParameters;
144
+ modelVersion?: string | null;
145
+ appId?: string | null;
146
+ meta?: CerMeta;
147
+ executionId?: string;
148
+ }
149
+ interface WrappedExecutionResult {
150
+ output: string | Record<string, unknown>;
151
+ snapshot: AiExecutionSnapshotV1;
152
+ bundle: CerAiExecutionBundle;
153
+ }
154
+
155
+ export type { AiExecutionSnapshotV1 as A, CreateSnapshotParams as C, ProviderConfig as P, RunBuilderOptions as R, StepParams as S, VerificationResult as V, WrappedExecutionParams as W, CerMeta as a, CerAiExecutionBundle as b, CertifyDecisionParams as c, RunSummary as d, AttestOptions as e, AttestationResult as f, AiExecutionParameters as g, WrappedExecutionResult as h };
package/package.json CHANGED
@@ -1,31 +1,39 @@
1
1
  {
2
2
  "name": "@nexart/ai-execution",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "AI Execution Integrity — tamper-evident records and Certified Execution Records (CER) for AI operations",
5
5
  "type": "module",
6
- "main": "./dist/index.js",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
7
8
  "types": "./dist/index.d.ts",
8
9
  "exports": {
9
10
  ".": {
10
11
  "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
12
14
  },
13
15
  "./providers/openai": {
14
16
  "types": "./dist/providers/openai.d.ts",
15
- "import": "./dist/providers/openai.js"
17
+ "import": "./dist/providers/openai.mjs",
18
+ "require": "./dist/providers/openai.cjs"
16
19
  },
17
20
  "./providers/anthropic": {
18
21
  "types": "./dist/providers/anthropic.d.ts",
19
- "import": "./dist/providers/anthropic.js"
22
+ "import": "./dist/providers/anthropic.mjs",
23
+ "require": "./dist/providers/anthropic.cjs"
20
24
  },
21
25
  "./providers/wrap": {
22
26
  "types": "./dist/providers/wrap.d.ts",
23
- "import": "./dist/providers/wrap.js"
24
- }
27
+ "import": "./dist/providers/wrap.mjs",
28
+ "require": "./dist/providers/wrap.cjs"
29
+ },
30
+ "./package.json": "./package.json"
25
31
  },
26
32
  "scripts": {
27
- "build": "tsc",
28
- "test": "node --test dist/__tests__/vectors.test.js dist/__tests__/fixtures.test.js dist/__tests__/v020.test.js",
33
+ "build": "tsup",
34
+ "build:tsc": "tsc",
35
+ "test": "tsc && node --test dist-tsc/__tests__/vectors.test.js dist-tsc/__tests__/fixtures.test.js dist-tsc/__tests__/v020.test.js dist-tsc/__tests__/v030.test.js dist-tsc/__tests__/v040.test.js",
36
+ "release": "npm run build && npm run test && npm version patch && npm publish --access public",
29
37
  "prepublishOnly": "npm run build && npm run test"
30
38
  },
31
39
  "keywords": [
@@ -52,6 +60,7 @@
52
60
  "README.md"
53
61
  ],
54
62
  "devDependencies": {
63
+ "tsup": "^8.0.0",
55
64
  "typescript": "^5.0.0"
56
65
  }
57
66
  }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=fixtures.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fixtures.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fixtures.test.ts"],"names":[],"mappings":""}
@@ -1,37 +0,0 @@
1
- import { describe, it } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { readFileSync } from 'node:fs';
4
- import { fileURLToPath } from 'node:url';
5
- import { dirname, join } from 'node:path';
6
- import { verifySnapshot } from '../snapshot.js';
7
- import { sealCer, verifyCer } from '../cer.js';
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
- const fixturesDir = join(__dirname, '..', '..', 'fixtures', 'vectors');
11
- function loadJson(filename) {
12
- return JSON.parse(readFileSync(join(fixturesDir, filename), 'utf-8'));
13
- }
14
- describe('fixture: vector-001', () => {
15
- const snapshot = loadJson('vector-001.snapshot.json');
16
- const expected = loadJson('vector-001.expected.json');
17
- it('snapshot inputHash matches expected', () => {
18
- assert.equal(snapshot.inputHash, expected.inputHash);
19
- });
20
- it('snapshot outputHash matches expected', () => {
21
- assert.equal(snapshot.outputHash, expected.outputHash);
22
- });
23
- it('snapshot passes verification', () => {
24
- const result = verifySnapshot(snapshot);
25
- assert.equal(result.ok, true, `verification errors: ${result.errors.join('; ')}`);
26
- });
27
- it('CER certificateHash matches expected', () => {
28
- const bundle = sealCer(snapshot, { createdAt: expected.cerCreatedAt });
29
- assert.equal(bundle.certificateHash, expected.certificateHash);
30
- });
31
- it('CER bundle passes verification', () => {
32
- const bundle = sealCer(snapshot, { createdAt: expected.cerCreatedAt });
33
- const result = verifyCer(bundle);
34
- assert.equal(result.ok, true, `verification errors: ${result.errors.join('; ')}`);
35
- });
36
- });
37
- //# sourceMappingURL=fixtures.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fixtures.test.js","sourceRoot":"","sources":["../../src/__tests__/fixtures.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG/C,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AACtC,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;AAEvE,SAAS,QAAQ,CAAC,QAAgB;IAChC,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,MAAM,QAAQ,GAAG,QAAQ,CAAC,0BAA0B,CAA0B,CAAC;IAC/E,MAAM,QAAQ,GAAG,QAAQ,CAAC,0BAA0B,CAKnD,CAAC;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,wBAAwB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC;QACvE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,eAAe,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC;QACvE,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,wBAAwB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=v020.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"v020.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/v020.test.ts"],"names":[],"mappings":""}
@@ -1,408 +0,0 @@
1
- import { describe, it } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { createSnapshot, verifySnapshot } from '../snapshot.js';
4
- import { sealCer, verifyCer } from '../cer.js';
5
- import { certifyDecision } from '../certify.js';
6
- import { RunBuilder } from '../run.js';
7
- import { exportCer, importCer } from '../archive.js';
8
- import { wrapProvider } from '../providers/wrap.js';
9
- import { CerVerificationError, CerAttestationError } from '../errors.js';
10
- import { readFileSync } from 'node:fs';
11
- import { fileURLToPath } from 'node:url';
12
- import { dirname, join } from 'node:path';
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = dirname(__filename);
15
- const fixturesDir = join(__dirname, '..', '..', 'fixtures');
16
- function loadJson(path) {
17
- return JSON.parse(readFileSync(join(fixturesDir, path), 'utf-8'));
18
- }
19
- const FIXED_TS = '2026-03-01T00:00:00.000Z';
20
- const BASE_PARAMS = {
21
- temperature: 0.7,
22
- maxTokens: 1024,
23
- topP: null,
24
- seed: null,
25
- };
26
- describe('backward compatibility: v0.1.0 bundles verify with v0.2.0', () => {
27
- it('golden-001 (text) verifies', () => {
28
- const bundle = loadJson('golden/golden-001.json');
29
- const result = verifyCer(bundle);
30
- assert.equal(result.ok, true, `errors: ${result.errors.join('; ')}`);
31
- });
32
- it('golden-002 (JSON i/o) verifies', () => {
33
- const bundle = loadJson('golden/golden-002.json');
34
- const result = verifyCer(bundle);
35
- assert.equal(result.ok, true, `errors: ${result.errors.join('; ')}`);
36
- });
37
- it('golden-003 (with meta) verifies', () => {
38
- const bundle = loadJson('golden/golden-003.json');
39
- const result = verifyCer(bundle);
40
- assert.equal(result.ok, true, `errors: ${result.errors.join('; ')}`);
41
- });
42
- it('golden-004 (all optional fields null) verifies', () => {
43
- const bundle = loadJson('golden/golden-004.json');
44
- const result = verifyCer(bundle);
45
- assert.equal(result.ok, true, `errors: ${result.errors.join('; ')}`);
46
- });
47
- it('golden-005 (seed+topP set) verifies', () => {
48
- const bundle = loadJson('golden/golden-005.json');
49
- const result = verifyCer(bundle);
50
- assert.equal(result.ok, true, `errors: ${result.errors.join('; ')}`);
51
- });
52
- });
53
- describe('certifyDecision', () => {
54
- it('produces equivalent output to createSnapshot + sealCer', () => {
55
- const params = {
56
- executionId: 'cd-test-001',
57
- timestamp: FIXED_TS,
58
- provider: 'openai',
59
- model: 'gpt-4o',
60
- prompt: 'Test prompt.',
61
- input: 'Test input.',
62
- parameters: BASE_PARAMS,
63
- output: 'Test output.',
64
- };
65
- const snapshot = createSnapshot(params);
66
- const bundle = sealCer(snapshot);
67
- const cdBundle = certifyDecision(params);
68
- assert.equal(cdBundle.bundleType, bundle.bundleType);
69
- assert.equal(cdBundle.version, bundle.version);
70
- assert.equal(cdBundle.snapshot.inputHash, bundle.snapshot.inputHash);
71
- assert.equal(cdBundle.snapshot.outputHash, bundle.snapshot.outputHash);
72
- assert.equal(cdBundle.snapshot.executionId, bundle.snapshot.executionId);
73
- });
74
- it('auto-generates executionId when not provided', () => {
75
- const bundle = certifyDecision({
76
- timestamp: FIXED_TS,
77
- provider: 'openai',
78
- model: 'gpt-4o',
79
- prompt: 'p',
80
- input: 'i',
81
- parameters: BASE_PARAMS,
82
- output: 'o',
83
- });
84
- assert.ok(bundle.snapshot.executionId.length > 0);
85
- assert.equal(bundle.bundleType, 'cer.ai.execution.v1');
86
- });
87
- it('passes meta through to bundle', () => {
88
- const bundle = certifyDecision({
89
- timestamp: FIXED_TS,
90
- provider: 'openai',
91
- model: 'gpt-4o',
92
- prompt: 'p',
93
- input: 'i',
94
- parameters: BASE_PARAMS,
95
- output: 'o',
96
- meta: { source: 'test', tags: ['cd'] },
97
- });
98
- assert.deepStrictEqual(bundle.meta, { source: 'test', tags: ['cd'] });
99
- });
100
- it('includes workflow fields in snapshot', () => {
101
- const bundle = certifyDecision({
102
- executionId: 'cd-wf-001',
103
- timestamp: FIXED_TS,
104
- provider: 'openai',
105
- model: 'gpt-4o',
106
- prompt: 'p',
107
- input: 'i',
108
- parameters: BASE_PARAMS,
109
- output: 'o',
110
- runId: 'run-abc',
111
- stepId: 'step-0',
112
- stepIndex: 0,
113
- workflowId: 'wf-xyz',
114
- conversationId: 'conv-123',
115
- prevStepHash: null,
116
- });
117
- assert.equal(bundle.snapshot.runId, 'run-abc');
118
- assert.equal(bundle.snapshot.stepId, 'step-0');
119
- assert.equal(bundle.snapshot.stepIndex, 0);
120
- assert.equal(bundle.snapshot.workflowId, 'wf-xyz');
121
- assert.equal(bundle.snapshot.conversationId, 'conv-123');
122
- assert.equal(bundle.snapshot.prevStepHash, null);
123
- });
124
- });
125
- describe('RunBuilder', () => {
126
- const stepBase = {
127
- provider: 'openai',
128
- model: 'gpt-4o',
129
- prompt: 'Step prompt.',
130
- parameters: BASE_PARAMS,
131
- };
132
- it('chains prevStepHash correctly across 3 steps', () => {
133
- const run = new RunBuilder({ runId: 'run-chain-test', workflowId: 'wf-1' });
134
- const b0 = run.step({ ...stepBase, input: 'Step 0 input', output: 'Step 0 output', timestamp: FIXED_TS });
135
- const b1 = run.step({ ...stepBase, input: 'Step 1 input', output: 'Step 1 output', timestamp: FIXED_TS });
136
- const b2 = run.step({ ...stepBase, input: 'Step 2 input', output: 'Step 2 output', timestamp: FIXED_TS });
137
- assert.equal(b0.snapshot.prevStepHash, null);
138
- assert.equal(b1.snapshot.prevStepHash, b0.certificateHash);
139
- assert.equal(b2.snapshot.prevStepHash, b1.certificateHash);
140
- });
141
- it('auto-increments stepIndex', () => {
142
- const run = new RunBuilder({ runId: 'run-idx-test' });
143
- const b0 = run.step({ ...stepBase, input: 'a', output: 'b', timestamp: FIXED_TS });
144
- const b1 = run.step({ ...stepBase, input: 'c', output: 'd', timestamp: FIXED_TS });
145
- assert.equal(b0.snapshot.stepIndex, 0);
146
- assert.equal(b1.snapshot.stepIndex, 1);
147
- });
148
- it('sets runId on all steps', () => {
149
- const run = new RunBuilder({ runId: 'run-id-test' });
150
- const b0 = run.step({ ...stepBase, input: 'x', output: 'y', timestamp: FIXED_TS });
151
- assert.equal(b0.snapshot.runId, 'run-id-test');
152
- });
153
- it('finalize returns correct summary', () => {
154
- const run = new RunBuilder({ runId: 'run-fin', workflowId: 'wf-fin', conversationId: 'conv-fin' });
155
- const b0 = run.step({ ...stepBase, input: 'a', output: 'b', timestamp: FIXED_TS });
156
- const b1 = run.step({ ...stepBase, input: 'c', output: 'd', timestamp: FIXED_TS });
157
- const summary = run.finalize();
158
- assert.equal(summary.runId, 'run-fin');
159
- assert.equal(summary.workflowId, 'wf-fin');
160
- assert.equal(summary.conversationId, 'conv-fin');
161
- assert.equal(summary.stepCount, 2);
162
- assert.equal(summary.steps.length, 2);
163
- assert.equal(summary.steps[0].stepIndex, 0);
164
- assert.equal(summary.steps[0].prevStepHash, null);
165
- assert.equal(summary.steps[1].stepIndex, 1);
166
- assert.equal(summary.steps[1].prevStepHash, b0.certificateHash);
167
- assert.equal(summary.finalStepHash, b1.certificateHash);
168
- });
169
- it('each step bundle verifies independently', () => {
170
- const run = new RunBuilder({ runId: 'run-verify' });
171
- const b0 = run.step({ ...stepBase, input: 'a', output: 'b', timestamp: FIXED_TS });
172
- const b1 = run.step({ ...stepBase, input: 'c', output: 'd', timestamp: FIXED_TS });
173
- assert.equal(verifyCer(b0).ok, true);
174
- assert.equal(verifyCer(b1).ok, true);
175
- });
176
- it('generates executionId from runId + stepIndex', () => {
177
- const run = new RunBuilder({ runId: 'run-eid' });
178
- const b0 = run.step({ ...stepBase, input: 'a', output: 'b', timestamp: FIXED_TS });
179
- assert.equal(b0.snapshot.executionId, 'run-eid-step-0');
180
- });
181
- });
182
- describe('exportCer / importCer', () => {
183
- it('round-trips a valid bundle', () => {
184
- const snapshot = createSnapshot({
185
- executionId: 'rt-001',
186
- timestamp: FIXED_TS,
187
- provider: 'openai',
188
- model: 'gpt-4o',
189
- prompt: 'p',
190
- input: 'i',
191
- parameters: BASE_PARAMS,
192
- output: 'o',
193
- });
194
- const bundle = sealCer(snapshot, { createdAt: FIXED_TS });
195
- const exported = exportCer(bundle);
196
- const imported = importCer(exported);
197
- assert.equal(imported.certificateHash, bundle.certificateHash);
198
- assert.equal(imported.snapshot.inputHash, bundle.snapshot.inputHash);
199
- assert.equal(imported.snapshot.outputHash, bundle.snapshot.outputHash);
200
- });
201
- it('rejects tampered JSON', () => {
202
- const snapshot = createSnapshot({
203
- executionId: 'rt-tamper',
204
- timestamp: FIXED_TS,
205
- provider: 'openai',
206
- model: 'gpt-4o',
207
- prompt: 'p',
208
- input: 'i',
209
- parameters: BASE_PARAMS,
210
- output: 'o',
211
- });
212
- const bundle = sealCer(snapshot, { createdAt: FIXED_TS });
213
- const exported = exportCer(bundle);
214
- const tampered = exported.replace('"o"', '"tampered"');
215
- assert.throws(() => importCer(tampered), CerVerificationError);
216
- });
217
- it('rejects invalid JSON', () => {
218
- assert.throws(() => importCer('not json'), CerVerificationError);
219
- });
220
- it('rejects wrong bundleType', () => {
221
- assert.throws(() => importCer('{"bundleType":"wrong"}'), CerVerificationError);
222
- });
223
- });
224
- describe('wrapProvider', () => {
225
- it('wraps a custom provider', async () => {
226
- const mockOutput = { text: 'Hello from mock' };
227
- const wrapped = wrapProvider({
228
- provider: 'mock-provider',
229
- callFn: async (_input) => mockOutput,
230
- extractOutput: (raw) => raw.text,
231
- });
232
- const result = await wrapped.execute({
233
- providerInput: { prompt: 'test' },
234
- prompt: 'System prompt',
235
- input: 'User input',
236
- model: 'mock-v1',
237
- parameters: BASE_PARAMS,
238
- });
239
- assert.equal(result.output, 'Hello from mock');
240
- assert.equal(result.snapshot.provider, 'mock-provider');
241
- assert.equal(result.snapshot.model, 'mock-v1');
242
- assert.equal(result.bundle.bundleType, 'cer.ai.execution.v1');
243
- assert.equal(verifyCer(result.bundle).ok, true);
244
- });
245
- it('uses extractModelVersion when provided', async () => {
246
- const wrapped = wrapProvider({
247
- provider: 'versioned',
248
- callFn: async () => ({ text: 'ok', version: 'v2.1' }),
249
- extractOutput: (raw) => raw.text,
250
- extractModelVersion: (raw) => raw.version,
251
- });
252
- const result = await wrapped.execute({
253
- providerInput: {},
254
- prompt: 'p',
255
- input: 'i',
256
- model: 'm',
257
- parameters: BASE_PARAMS,
258
- });
259
- assert.equal(result.snapshot.modelVersion, 'v2.1');
260
- });
261
- });
262
- describe('typed errors', () => {
263
- it('CerVerificationError has errors array', () => {
264
- const err = new CerVerificationError(['e1', 'e2']);
265
- assert.equal(err.name, 'CerVerificationError');
266
- assert.deepStrictEqual(err.errors, ['e1', 'e2']);
267
- assert.ok(err.message.includes('e1'));
268
- assert.ok(err instanceof Error);
269
- });
270
- it('CerAttestationError has statusCode and responseBody', () => {
271
- const err = new CerAttestationError('fail', 401, { error: 'unauthorized' });
272
- assert.equal(err.name, 'CerAttestationError');
273
- assert.equal(err.statusCode, 401);
274
- assert.deepStrictEqual(err.responseBody, { error: 'unauthorized' });
275
- assert.ok(err instanceof Error);
276
- });
277
- });
278
- describe('snapshot workflow fields', () => {
279
- it('creates snapshot without workflow fields (backward compat)', () => {
280
- const snap = createSnapshot({
281
- executionId: 'no-wf',
282
- timestamp: FIXED_TS,
283
- provider: 'openai',
284
- model: 'gpt-4o',
285
- prompt: 'p',
286
- input: 'i',
287
- parameters: BASE_PARAMS,
288
- output: 'o',
289
- });
290
- assert.equal(snap.runId, undefined);
291
- assert.equal(snap.stepId, undefined);
292
- assert.equal(snap.stepIndex, undefined);
293
- });
294
- it('creates snapshot with workflow fields', () => {
295
- const snap = createSnapshot({
296
- executionId: 'wf-test',
297
- timestamp: FIXED_TS,
298
- provider: 'openai',
299
- model: 'gpt-4o',
300
- prompt: 'p',
301
- input: 'i',
302
- parameters: BASE_PARAMS,
303
- output: 'o',
304
- runId: 'run-1',
305
- stepId: 'step-0',
306
- stepIndex: 0,
307
- workflowId: 'wf-1',
308
- conversationId: 'conv-1',
309
- prevStepHash: null,
310
- });
311
- assert.equal(snap.runId, 'run-1');
312
- assert.equal(snap.stepId, 'step-0');
313
- assert.equal(snap.stepIndex, 0);
314
- assert.equal(snap.workflowId, 'wf-1');
315
- assert.equal(snap.conversationId, 'conv-1');
316
- assert.equal(snap.prevStepHash, null);
317
- });
318
- it('workflow fields included in certificate hash', () => {
319
- const params = {
320
- executionId: 'wf-hash-test',
321
- timestamp: FIXED_TS,
322
- provider: 'openai',
323
- model: 'gpt-4o',
324
- prompt: 'p',
325
- input: 'i',
326
- parameters: BASE_PARAMS,
327
- output: 'o',
328
- };
329
- const snap1 = createSnapshot(params);
330
- const snap2 = createSnapshot({ ...params, runId: 'run-x', stepIndex: 0 });
331
- const b1 = sealCer(snap1, { createdAt: FIXED_TS });
332
- const b2 = sealCer(snap2, { createdAt: FIXED_TS });
333
- assert.notEqual(b1.certificateHash, b2.certificateHash);
334
- });
335
- it('v0.2 snapshot verifies correctly', () => {
336
- const snap = createSnapshot({
337
- executionId: 'v02-verify',
338
- timestamp: FIXED_TS,
339
- provider: 'openai',
340
- model: 'gpt-4o',
341
- prompt: 'p',
342
- input: 'i',
343
- parameters: BASE_PARAMS,
344
- output: 'o',
345
- runId: 'r',
346
- stepIndex: 0,
347
- });
348
- assert.equal(verifySnapshot(snap).ok, true);
349
- });
350
- });
351
- describe('fixture: vector-002 (3-step chain)', () => {
352
- const chain = loadJson('vectors/vector-002.chain.json');
353
- it('has 3 steps', () => {
354
- assert.equal(chain.length, 3);
355
- });
356
- it('each step snapshot verifies', () => {
357
- for (const entry of chain) {
358
- const result = verifySnapshot(entry.snapshot);
359
- assert.equal(result.ok, true, `step ${entry.snapshot.stepIndex} errors: ${result.errors.join('; ')}`);
360
- }
361
- });
362
- it('each step CER hash matches expected', () => {
363
- for (const entry of chain) {
364
- const bundle = sealCer(entry.snapshot, { createdAt: entry.cerCreatedAt });
365
- assert.equal(bundle.certificateHash, entry.expectedCertificateHash, `step ${entry.snapshot.stepIndex} hash mismatch`);
366
- }
367
- });
368
- it('prevStepHash chain is correct', () => {
369
- assert.equal(chain[0].snapshot.prevStepHash, null);
370
- assert.equal(chain[1].snapshot.prevStepHash, chain[0].expectedCertificateHash);
371
- assert.equal(chain[2].snapshot.prevStepHash, chain[1].expectedCertificateHash);
372
- });
373
- it('stepIndex is sequential', () => {
374
- assert.equal(chain[0].snapshot.stepIndex, 0);
375
- assert.equal(chain[1].snapshot.stepIndex, 1);
376
- assert.equal(chain[2].snapshot.stepIndex, 2);
377
- });
378
- });
379
- describe('sdkVersion updated to 0.2.0', () => {
380
- it('new snapshots default to 0.2.0', () => {
381
- const snap = createSnapshot({
382
- executionId: 'ver-test',
383
- timestamp: FIXED_TS,
384
- provider: 'openai',
385
- model: 'gpt-4o',
386
- prompt: 'p',
387
- input: 'i',
388
- parameters: BASE_PARAMS,
389
- output: 'o',
390
- });
391
- assert.equal(snap.sdkVersion, '0.2.0');
392
- });
393
- it('explicit sdkVersion overrides default', () => {
394
- const snap = createSnapshot({
395
- executionId: 'ver-override',
396
- timestamp: FIXED_TS,
397
- provider: 'openai',
398
- model: 'gpt-4o',
399
- prompt: 'p',
400
- input: 'i',
401
- parameters: BASE_PARAMS,
402
- output: 'o',
403
- sdkVersion: '0.1.0',
404
- });
405
- assert.equal(snap.sdkVersion, '0.1.0');
406
- });
407
- });
408
- //# sourceMappingURL=v020.test.js.map