mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
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,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user