Files
qinglong/packages/ql3-runtime-core/test/toolExecutionEvidence.test.cjs

262 lines
7.7 KiB
JavaScript

const assert = require('node:assert/strict');
const { readFileSync } = require('node:fs');
const { join } = require('node:path');
const { test } = require('node:test');
const {
InvalidToolExecutionEvidenceError,
MAX_TOOL_EXECUTION_EVIDENCE_PAGE_SIZE,
TOOL_EXECUTION_AUDIT_RECEIPT_SCHEMA,
TOOL_EXECUTION_EVIDENCE_BUNDLE_SCHEMA,
TOOL_EXECUTION_START_AUDIT_OPERATION,
TOOL_EXECUTION_TRACE_ANCHOR_SCHEMA,
createToolExecutionEvidenceBundle,
normalizeListToolExecutionEvidenceQuery,
normalizeListToolExecutionEvidenceResult,
normalizeToolExecutionAuditReceipt,
normalizeToolExecutionEvidenceBundle,
normalizeToolExecutionTraceAnchor,
toolExecutionAdmissionEvidence,
toolExecutionAuditRecordDigest,
} = require('../dist/tool-execution/toolExecutionEvidence');
const DIGEST_A = 'a'.repeat(64);
const DIGEST_B = 'b'.repeat(64);
const DIGEST_C = 'c'.repeat(64);
const DIGEST_D = 'd'.repeat(64);
const DIGEST_E = 'e'.repeat(64);
function audit(overrides = {}) {
return {
eventId: '40000000-0000-4000-8000-000000000001',
requestId: 'tool-request-001',
operationId: TOOL_EXECUTION_START_AUDIT_OPERATION,
projectId: 'project-001',
subject: { type: 'agent', id: 'agent-001' },
authenticationId: 'auth-agent-001',
outcome: 'allowed',
reasons: ['tool_execution_start'],
fence: { projectVersion: 3, bindingVersion: 4 },
occurredAtMs: 1_000,
...overrides,
};
}
function input(overrides = {}) {
return {
traceId: '1'.repeat(32),
spanId: '2'.repeat(16),
projectId: 'project-001',
runId: 'run-001',
stepRunId: 'step-run-001',
invocationPlanDigest: DIGEST_A,
bindingDigest: DIGEST_B,
adapterDigest: DIGEST_C,
redactionContractDigest: DIGEST_D,
auditContractDigest: DIGEST_E,
audit: audit(),
createdAtMs: 1_000,
...overrides,
};
}
function copy(value) {
return structuredClone(value);
}
test('creates one immutable low-sensitive Trace and Audit evidence bundle', () => {
const bundle = createToolExecutionEvidenceBundle(input());
assert.equal(bundle.schema, TOOL_EXECUTION_EVIDENCE_BUNDLE_SCHEMA);
assert.equal(bundle.trace.schema, TOOL_EXECUTION_TRACE_ANCHOR_SCHEMA);
assert.equal(bundle.receipt.schema, TOOL_EXECUTION_AUDIT_RECEIPT_SCHEMA);
assert.equal(bundle.trace.parentSpanId, null);
assert.match(bundle.trace.traceDigest, /^[0-9a-f]{64}$/);
assert.match(bundle.receipt.auditRecordDigest, /^[0-9a-f]{64}$/);
assert.match(bundle.receipt.receiptDigest, /^[0-9a-f]{64}$/);
assert.equal(Object.isFrozen(bundle), true);
assert.equal(Object.isFrozen(bundle.trace), true);
assert.equal(Object.isFrozen(bundle.audit), true);
assert.equal(Object.isFrozen(bundle.receipt), true);
assert.deepEqual(normalizeToolExecutionEvidenceBundle(bundle), bundle);
assert.deepEqual(toolExecutionAdmissionEvidence(bundle), {
trace: {
traceId: bundle.trace.traceId,
spanId: bundle.trace.spanId,
digest: bundle.trace.traceDigest,
},
audit: {
eventId: bundle.audit.eventId,
digest: bundle.receipt.receiptDigest,
},
});
});
test('uses canonical domain-separated digests and binds every authority fact', () => {
const first = createToolExecutionEvidenceBundle(input());
const replay = createToolExecutionEvidenceBundle(input());
assert.deepEqual(replay, first);
assert.equal(
toolExecutionAuditRecordDigest(first.audit),
first.receipt.auditRecordDigest,
);
const changed = createToolExecutionEvidenceBundle(
input({
adapterDigest: 'f'.repeat(64),
audit: audit({
eventId: '40000000-0000-4000-8000-000000000002',
}),
}),
);
assert.notEqual(changed.trace.traceDigest, first.trace.traceDigest);
assert.notEqual(changed.receipt.receiptDigest, first.receipt.receiptDigest);
});
test('rejects non-start, denied, unfenced and cross-Project audit records', () => {
for (const invalidAudit of [
audit({ operationId: 'tool.invoke.finish' }),
audit({ outcome: 'denied' }),
audit({ fence: null }),
audit({ projectId: 'project-other' }),
audit({ occurredAtMs: 999 }),
]) {
assert.throws(
() => createToolExecutionEvidenceBundle(input({ audit: invalidAudit })),
InvalidToolExecutionEvidenceError,
);
}
});
test('rejects trace identity, parent loops, unknown fields and digest tampering', () => {
assert.throws(
() => createToolExecutionEvidenceBundle(input({ traceId: '1'.repeat(31) })),
InvalidToolExecutionEvidenceError,
);
assert.throws(
() =>
createToolExecutionEvidenceBundle(
input({ parentSpanId: '2'.repeat(16) }),
),
InvalidToolExecutionEvidenceError,
);
assert.throws(
() => createToolExecutionEvidenceBundle({ ...input(), secret: 'value' }),
InvalidToolExecutionEvidenceError,
);
const bundle = createToolExecutionEvidenceBundle(input());
assert.throws(
() =>
normalizeToolExecutionTraceAnchor({
...copy(bundle.trace),
traceDigest: '0'.repeat(64),
}),
InvalidToolExecutionEvidenceError,
);
assert.throws(
() =>
normalizeToolExecutionAuditReceipt({
...copy(bundle.receipt),
receiptDigest: '0'.repeat(64),
}),
InvalidToolExecutionEvidenceError,
);
});
test('rejects detached Trace, Audit and receipt relationships', () => {
const bundle = createToolExecutionEvidenceBundle(input());
const other = createToolExecutionEvidenceBundle(
input({
traceId: '3'.repeat(32),
spanId: '4'.repeat(16),
stepRunId: 'step-run-002',
audit: audit({
eventId: '40000000-0000-4000-8000-000000000002',
}),
}),
);
assert.throws(
() =>
normalizeToolExecutionEvidenceBundle({
...copy(bundle),
receipt: other.receipt,
}),
InvalidToolExecutionEvidenceError,
);
assert.throws(
() =>
normalizeToolExecutionEvidenceBundle({
...copy(bundle),
audit: other.audit,
}),
InvalidToolExecutionEvidenceError,
);
});
test('normalizes bounded stable evidence pagination', () => {
const first = createToolExecutionEvidenceBundle(input());
const second = createToolExecutionEvidenceBundle(
input({
traceId: '3'.repeat(32),
spanId: '4'.repeat(16),
audit: audit({
eventId: '40000000-0000-4000-8000-000000000002',
occurredAtMs: 1_001,
}),
createdAtMs: 1_001,
}),
);
const query = normalizeListToolExecutionEvidenceQuery({
runId: 'run-001',
limit: 2,
});
assert.deepEqual(
normalizeListToolExecutionEvidenceResult(
{
bundles: [first, second],
truncated: true,
next: {
createdAtMs: second.trace.createdAtMs,
traceId: second.trace.traceId,
spanId: second.trace.spanId,
},
},
query,
).bundles,
[first, second],
);
assert.throws(
() =>
normalizeListToolExecutionEvidenceQuery({
runId: 'run-001',
limit: MAX_TOOL_EXECUTION_EVIDENCE_PAGE_SIZE + 1,
}),
InvalidToolExecutionEvidenceError,
);
assert.throws(
() =>
normalizeListToolExecutionEvidenceResult(
{ bundles: [second, first], truncated: false },
query,
),
InvalidToolExecutionEvidenceError,
);
});
test('publishes the same pure contract through root and explicit subpath', () => {
const root = require('../dist');
const subpath = require('../dist/tool-execution/toolExecutionEvidence');
assert.equal(
root.createToolExecutionEvidenceBundle,
subpath.createToolExecutionEvidenceBundle,
);
const source = readFileSync(
join(__dirname, '../src/tool-execution/toolExecutionEvidence.ts'),
'utf8',
);
assert.doesNotMatch(
source,
/node:(?:fs|child_process|net|http|https)|setInterval|setTimeout|execute|handler/i,
);
});