mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 00:38:14 +08:00
1027 lines
32 KiB
JavaScript
1027 lines
32 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { createHash } = require('node:crypto');
|
|
const { DatabaseSync } = require('node:sqlite');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
createPluginPackageResourceGenerationFromReferences,
|
|
} = require('@qinglong/runtime-core/plugin-package-resource-generation');
|
|
const {
|
|
createPluginPackageQuarantineEvent,
|
|
} = require('@qinglong/runtime-core/plugin-package-quarantine');
|
|
const {
|
|
createProjectToolDefinitionSnapshot,
|
|
projectToolDefinitionRegistry,
|
|
} = require('@qinglong/runtime-core/project-tool-definition-snapshot');
|
|
const {
|
|
createStepRunMutation,
|
|
transitionStepRunMutation,
|
|
} = require('@qinglong/runtime-core/step-run');
|
|
const {
|
|
ToolExecutionStartBarrierConflictError,
|
|
ToolExecutionStartBarrierUnavailableError,
|
|
createToolExecutionStartCommand,
|
|
} = require('@qinglong/runtime-core/tool-execution-start-barrier');
|
|
const {
|
|
TOOL_EXECUTION_START_AUDIT_OPERATION,
|
|
createToolExecutionEvidenceBundle,
|
|
toolExecutionAdmissionEvidence,
|
|
} = require('@qinglong/runtime-core/tool-execution-evidence');
|
|
const {
|
|
TrustedToolHandlerBindingRegistry,
|
|
admitTrustedToolExecution,
|
|
createTrustedToolHandlerBinding,
|
|
createTrustedToolInvocationPlan,
|
|
trustedToolContractIdentityDigest,
|
|
} = require('@qinglong/runtime-core/trusted-tool-invocation');
|
|
const {
|
|
prepareToolInvocation,
|
|
} = require('@qinglong/runtime-core/tool-registry');
|
|
const {
|
|
ToolExecutionCompletionConflictError,
|
|
createToolExecutionCompletionCommand,
|
|
createToolExecutionResultArtifact,
|
|
openToolExecutionResultArtifact,
|
|
} = require('@qinglong/runtime-core/tool-execution-completion');
|
|
const {
|
|
TOOL_EXECUTION_FAILURE_FACTS,
|
|
ToolExecutionFailureCompletionConflictError,
|
|
createToolExecutionFailureCompletionCommand,
|
|
createToolExecutionFailureResult,
|
|
} = require('@qinglong/runtime-core/tool-execution-failure-completion');
|
|
const {
|
|
TRUSTED_TOOL_EXECUTION_RESULT_SCHEMA,
|
|
} = require('@qinglong/runtime-core/trusted-tool-execution');
|
|
const {
|
|
createToolResultKeyCatalogBootstrapCommand,
|
|
requireActiveToolResultKey,
|
|
toolResultKeyCatalogFence,
|
|
toolResultKeyMaterialProof,
|
|
} = require('@qinglong/runtime-core/tool-result-key-catalog');
|
|
|
|
const {
|
|
LocalSqliteOperationAuthority,
|
|
} = require('../dist/authority/operationAuthority');
|
|
const { migrateLocalSqliteDatabase } = require('../dist/migration/migration');
|
|
const {
|
|
LocalSqliteStepRunRepository,
|
|
} = require('../dist/run/stepRunRepository');
|
|
const {
|
|
LocalSqliteToolExecutionStartBarrierRepository,
|
|
} = require('../dist/tool-execution/toolExecutionStartBarrierRepository');
|
|
const {
|
|
LocalSqliteToolInvocationArtifactRepository,
|
|
} = require('../dist/tool-execution/toolInvocationArtifactRepository');
|
|
const {
|
|
LocalSqliteToolExecutionCompletionRepository,
|
|
} = require('../dist/tool-execution/toolExecutionCompletionRepository');
|
|
const {
|
|
LocalSqliteToolExecutionFailureCompletionRepository,
|
|
} = require('../dist/tool-execution/toolExecutionFailureCompletionRepository');
|
|
const {
|
|
LocalSqliteToolResultKeyCatalogRepository,
|
|
} = require('../dist/tool-execution/toolResultKeyCatalogRepository');
|
|
|
|
const DIGEST_A = 'a'.repeat(64);
|
|
const DIGEST_B = 'b'.repeat(64);
|
|
const DIGEST_C = 'c'.repeat(64);
|
|
const REQUESTER = Object.freeze({ type: 'user', id: 'usr-tool-owner' });
|
|
const FENCE = Object.freeze({ projectVersion: 3, bindingVersion: 7 });
|
|
const NOW_MS = 1_400;
|
|
const OUTPUT_DIGEST_DOMAIN = Buffer.from(
|
|
'qinglong/trusted-tool-execution-output-digest@v1\0',
|
|
'utf8',
|
|
);
|
|
const RESULT_DIGEST_DOMAIN = Buffer.from(
|
|
'qinglong/trusted-tool-execution-result-digest@v1\0',
|
|
'utf8',
|
|
);
|
|
|
|
function hash(domain, value) {
|
|
return createHash('sha256')
|
|
.update(domain)
|
|
.update(JSON.stringify(value))
|
|
.digest('hex');
|
|
}
|
|
|
|
function authorizer() {
|
|
return {
|
|
async authorize() {
|
|
return { effect: 'allow', reasons: ['role_grant'], fence: FENCE };
|
|
},
|
|
};
|
|
}
|
|
|
|
function principal() {
|
|
return {
|
|
subject: REQUESTER,
|
|
authenticationId: 'auth-tool-1',
|
|
authenticatedAtMs: 800,
|
|
expiresAtMs: 10_000,
|
|
assurance: 'local_console',
|
|
};
|
|
}
|
|
|
|
function projectToolSnapshot() {
|
|
const generation = createPluginPackageResourceGenerationFromReferences({
|
|
installationId: 'install-demo',
|
|
projectId: 'project-001',
|
|
packageName: 'demo',
|
|
lockDigest: DIGEST_A,
|
|
generation: 1,
|
|
previousActiveLockDigest: null,
|
|
contentDigest: DIGEST_B,
|
|
resources: [],
|
|
});
|
|
return createProjectToolDefinitionSnapshot({
|
|
projectId: 'project-001',
|
|
contributions: [
|
|
{
|
|
generation,
|
|
revisionDigest: DIGEST_C,
|
|
definitions: [
|
|
{
|
|
name: 'demo.compare',
|
|
version: '1.0.0',
|
|
description: 'Compare one bounded Run projection',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
runId: { type: 'string', minLength: 1, maxLength: 64 },
|
|
},
|
|
required: ['runId'],
|
|
additionalProperties: false,
|
|
},
|
|
outputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
summary: { type: 'string', maxLength: 1024 },
|
|
},
|
|
required: ['summary'],
|
|
additionalProperties: false,
|
|
},
|
|
effect: 'read',
|
|
risk: 'low',
|
|
requiredPermissions: ['run.read'],
|
|
timeoutSeconds: 30,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
async function harness() {
|
|
const client = new DatabaseSync(':memory:');
|
|
client.exec('PRAGMA foreign_keys = ON');
|
|
await migrateLocalSqliteDatabase(client);
|
|
client.exec(`
|
|
INSERT INTO "QingLong3Projects" (
|
|
id, name, slug, status, version, created_at_ms, updated_at_ms
|
|
) VALUES (
|
|
'project-001', 'Project', 'project-001', 'active', 1, 1, 1
|
|
);
|
|
INSERT INTO "Runs" (
|
|
id, project_id, task_id, task_revision, trigger_type,
|
|
execution_origin, execution_owner, status, version,
|
|
event_sequence, priority, created_at_ms
|
|
) VALUES (
|
|
'run-001', 'project-001', 'task-001', 'revision-001', 'manual',
|
|
'manual', 'runtime', 'running', 0, 0, 0, 1
|
|
);
|
|
`);
|
|
const authority = new LocalSqliteOperationAuthority(client);
|
|
const stepRuns = new LocalSqliteStepRunRepository(authority);
|
|
const starts = new LocalSqliteToolExecutionStartBarrierRepository(authority);
|
|
const artifacts = new LocalSqliteToolInvocationArtifactRepository(authority);
|
|
const completions = new LocalSqliteToolExecutionCompletionRepository(
|
|
authority,
|
|
);
|
|
const failureCompletions =
|
|
new LocalSqliteToolExecutionFailureCompletionRepository(authority);
|
|
const resultKeyCatalog = new LocalSqliteToolResultKeyCatalogRepository(
|
|
authority,
|
|
);
|
|
const resultKeyCatalogCommit = await resultKeyCatalog.append(
|
|
createToolResultKeyCatalogBootstrapCommand({
|
|
keyId: 'tool-result-key-test',
|
|
materialProof: toolResultKeyMaterialProof(
|
|
'tool-result-key-test',
|
|
Buffer.alloc(32, 5),
|
|
),
|
|
mutationId: 'tool-result-key-bootstrap-test',
|
|
}),
|
|
);
|
|
const resultKeyCatalogFence = toolResultKeyCatalogFence(
|
|
resultKeyCatalogCommit.catalog,
|
|
requireActiveToolResultKey(resultKeyCatalogCommit.catalog),
|
|
);
|
|
const toolSnapshot = projectToolSnapshot();
|
|
const creation = createStepRunMutation(
|
|
{
|
|
id: 'step-run-001',
|
|
runId: 'run-001',
|
|
stepKey: 'workflow.compare',
|
|
kind: 'tool',
|
|
definitionRef: 'tool:demo.compare@1.0.0',
|
|
definitionDigest: toolSnapshot.definitions[0].definitionDigest,
|
|
required: true,
|
|
initialStatus: 'ready',
|
|
inputRef: 'artifact:step-input-001',
|
|
mutationId: 'step-create-001',
|
|
createdAtMs: 1_000,
|
|
},
|
|
{
|
|
expectedRunVersion: 0,
|
|
expectedRunEventSequence: 0,
|
|
eventId: '50000000-0000-4000-8000-000000000001',
|
|
dedupeKey: 'step-create:step-run-001',
|
|
actor: REQUESTER,
|
|
},
|
|
);
|
|
await stepRuns.apply(creation);
|
|
return {
|
|
client,
|
|
authority,
|
|
stepRuns,
|
|
starts,
|
|
artifacts,
|
|
completions,
|
|
failureCompletions,
|
|
resultKeyCatalog,
|
|
resultKeyCatalogFence,
|
|
ready: creation.stepRun,
|
|
close: () => authority.close(),
|
|
};
|
|
}
|
|
|
|
function executionResult(barrier, output, completedAtMs) {
|
|
const outputDigest = hash(OUTPUT_DIGEST_DOMAIN, output);
|
|
const unsigned = Object.freeze({
|
|
schema: TRUSTED_TOOL_EXECUTION_RESULT_SCHEMA,
|
|
startId: barrier.startId,
|
|
barrierDigest: barrier.barrierDigest,
|
|
adapterDigest: barrier.adapterDigest,
|
|
output,
|
|
outputDigest,
|
|
completedAtMs,
|
|
});
|
|
return Object.freeze({
|
|
...unsigned,
|
|
resultDigest: hash(RESULT_DIGEST_DOMAIN, unsigned),
|
|
});
|
|
}
|
|
|
|
async function command(current, overrides = {}) {
|
|
const snapshot = projectToolSnapshot();
|
|
const binding = createTrustedToolHandlerBinding(snapshot, {
|
|
tool: { name: 'demo.compare', version: '1.0.0' },
|
|
adapter: { id: 'builtin.demo-compare', version: '1.0.0' },
|
|
executionClass: 'builtin_in_process',
|
|
profiles: ['edge', 'standalone'],
|
|
authorities: ['database.read'],
|
|
timeoutSeconds: 20,
|
|
redactionContract: {
|
|
id: 'redaction.demo-compare',
|
|
version: '1.0.0',
|
|
},
|
|
auditContract: { id: 'audit.tool-call', version: '1.0.0' },
|
|
});
|
|
assert.equal(binding.definitionDigest, current.ready.definitionDigest);
|
|
const bindings = new TrustedToolHandlerBindingRegistry(snapshot, [binding]);
|
|
const invocation = await prepareToolInvocation(
|
|
projectToolDefinitionRegistry(snapshot),
|
|
{
|
|
projectId: 'project-001',
|
|
principal: principal(),
|
|
nowMs: 900,
|
|
tool: { name: 'demo.compare', version: '1.0.0' },
|
|
input: { runId: 'run-001' },
|
|
},
|
|
authorizer(),
|
|
);
|
|
const bundle = createTrustedToolInvocationPlan(bindings, invocation, {
|
|
actionRef: 'tool-plan:run-001',
|
|
inputArtifactId: 'artifact-input-001',
|
|
previewArtifactId: 'artifact-preview-001',
|
|
artifactKeyId: 'tool-key-test',
|
|
artifactKey: Buffer.alloc(32, 7),
|
|
artifactNonce: Buffer.alloc(12, 9),
|
|
profile: 'edge',
|
|
preview: {
|
|
title: 'Compare Run',
|
|
summary: 'Reads one Run projection',
|
|
fields: [{ kind: 'identifier', label: 'Run', value: 'run-001' }],
|
|
warnings: [],
|
|
},
|
|
sealedAtMs: 1_000,
|
|
});
|
|
await current.artifacts.put(bundle.inputArtifact, bundle.previewArtifact);
|
|
const plan = bundle.plan;
|
|
const evidence = createToolExecutionEvidenceBundle({
|
|
traceId: overrides.traceId ?? '1'.repeat(32),
|
|
spanId: '2'.repeat(16),
|
|
projectId: 'project-001',
|
|
runId: 'run-001',
|
|
stepRunId: current.ready.id,
|
|
invocationPlanDigest: plan.planDigest,
|
|
bindingDigest: binding.bindingDigest,
|
|
adapterDigest: trustedToolContractIdentityDigest(binding.adapter),
|
|
redactionContractDigest: trustedToolContractIdentityDigest(
|
|
binding.redactionContract,
|
|
),
|
|
auditContractDigest: trustedToolContractIdentityDigest(
|
|
binding.auditContract,
|
|
),
|
|
audit: {
|
|
eventId: overrides.auditEventId ?? '40000000-0000-4000-8000-000000000001',
|
|
requestId: 'tool-request-001',
|
|
operationId: TOOL_EXECUTION_START_AUDIT_OPERATION,
|
|
projectId: 'project-001',
|
|
subject: REQUESTER,
|
|
authenticationId: 'auth-tool-1',
|
|
outcome: 'allowed',
|
|
reasons: ['tool_execution_start'],
|
|
fence: FENCE,
|
|
occurredAtMs: NOW_MS,
|
|
},
|
|
createdAtMs: NOW_MS,
|
|
});
|
|
const admission = await admitTrustedToolExecution(bindings, plan, {
|
|
principal: principal(),
|
|
profile: 'edge',
|
|
nowMs: NOW_MS,
|
|
authorizer: authorizer(),
|
|
evidence: {
|
|
stepRun: {
|
|
id: current.ready.id,
|
|
version: current.ready.version,
|
|
digest: current.ready.stepRunDigest,
|
|
},
|
|
...toolExecutionAdmissionEvidence(evidence),
|
|
},
|
|
});
|
|
const mutation = transitionStepRunMutation(
|
|
current.ready,
|
|
{
|
|
expectedVersion: current.ready.version,
|
|
expectedDigest: current.ready.stepRunDigest,
|
|
mutationId: overrides.mutationId ?? 'step-running-002',
|
|
to: 'running',
|
|
atMs: NOW_MS,
|
|
},
|
|
{
|
|
expectedRunVersion: 1,
|
|
expectedRunEventSequence: 1,
|
|
eventId: overrides.eventId ?? '50000000-0000-4000-8000-000000000002',
|
|
dedupeKey: 'step-running:step-run-001',
|
|
actor: REQUESTER,
|
|
},
|
|
);
|
|
return createToolExecutionStartCommand({
|
|
startId: overrides.startId ?? 'tool-start-001',
|
|
admission,
|
|
evidence,
|
|
stepRunMutation: mutation,
|
|
});
|
|
}
|
|
|
|
function counts(client) {
|
|
return {
|
|
...client
|
|
.prepare(
|
|
`SELECT
|
|
(SELECT COUNT(*) FROM "ToolExecutionStartBarriers") AS barriers,
|
|
(SELECT COUNT(*) FROM "ToolExecutionStartArtifactBindings")
|
|
AS bindings,
|
|
(SELECT COUNT(*) FROM "ToolExecutionTraceAnchors") AS traces,
|
|
(SELECT COUNT(*) FROM "ToolExecutionAuditReceipts") AS receipts,
|
|
(SELECT COUNT(*) FROM "QingLong3SecurityAuditEvents") AS audits`,
|
|
)
|
|
.get(),
|
|
};
|
|
}
|
|
|
|
test('atomically records evidence, running transition and exact replay', async () => {
|
|
const current = await harness();
|
|
try {
|
|
const start = await command(current);
|
|
const [first, replay] = await Promise.all([
|
|
current.starts.prepare(start),
|
|
current.starts.prepare(start),
|
|
]);
|
|
assert.deepEqual([first.status, replay.status].sort(), [
|
|
'created',
|
|
'existing',
|
|
]);
|
|
assert.deepEqual(counts(current.client), {
|
|
barriers: 1,
|
|
bindings: 1,
|
|
traces: 1,
|
|
receipts: 1,
|
|
audits: 1,
|
|
});
|
|
const step = await current.stepRuns.findById('step-run-001');
|
|
assert.equal(step.status, 'running');
|
|
assert.equal(step.version, 2);
|
|
assert.deepEqual(
|
|
await current.starts.findByStartId('tool-start-001'),
|
|
first.barrier,
|
|
);
|
|
assert.deepEqual(
|
|
await current.starts.findByStepRun('run-001', 'step-run-001', 2),
|
|
first.barrier,
|
|
);
|
|
const run = current.client
|
|
.prepare(
|
|
`SELECT version, event_sequence AS "eventSequence"
|
|
FROM "Runs" WHERE id = 'run-001'`,
|
|
)
|
|
.get();
|
|
assert.deepEqual({ ...run }, { version: 2, eventSequence: 2 });
|
|
} finally {
|
|
await current.close();
|
|
}
|
|
});
|
|
|
|
test('atomically seals result, succeeds StepRun and exactly replays completion', async () => {
|
|
const current = await harness();
|
|
try {
|
|
const prepared = await current.starts.prepare(await command(current));
|
|
const barrier = prepared.barrier;
|
|
const result = executionResult(
|
|
barrier,
|
|
{ summary: 'Run is healthy' },
|
|
1_500,
|
|
);
|
|
const registry = projectToolDefinitionRegistry(projectToolSnapshot());
|
|
const artifact = createToolExecutionResultArtifact(
|
|
{
|
|
artifactId: 'artifact-result-001',
|
|
projectId: barrier.projectId,
|
|
runId: barrier.runId,
|
|
stepRunId: barrier.stepRunId,
|
|
tool: { name: 'demo.compare', version: '1.0.0' },
|
|
executionResult: result,
|
|
keyId: 'tool-result-key-test',
|
|
key: Buffer.alloc(32, 5),
|
|
},
|
|
registry,
|
|
() => Buffer.alloc(12, 4),
|
|
);
|
|
const running = await current.stepRuns.findById('step-run-001');
|
|
const mutation = transitionStepRunMutation(
|
|
running,
|
|
{
|
|
expectedVersion: running.version,
|
|
expectedDigest: running.stepRunDigest,
|
|
mutationId: 'step-succeeded-003',
|
|
to: 'succeeded',
|
|
atMs: result.completedAtMs,
|
|
outputRef: artifact.artifactId,
|
|
},
|
|
{
|
|
expectedRunVersion: 2,
|
|
expectedRunEventSequence: 2,
|
|
eventId: '50000000-0000-4000-8000-000000000003',
|
|
dedupeKey: 'step-succeeded:step-run-001',
|
|
actor: REQUESTER,
|
|
},
|
|
);
|
|
const completionCommand = createToolExecutionCompletionCommand({
|
|
barrier,
|
|
executionResult: result,
|
|
resultArtifact: artifact,
|
|
resultKeyCatalogFence: current.resultKeyCatalogFence,
|
|
stepRunMutation: mutation,
|
|
});
|
|
const [first, replay] = await Promise.all([
|
|
current.completions.commit(completionCommand),
|
|
current.completions.commit(completionCommand),
|
|
]);
|
|
assert.deepEqual([first.status, replay.status].sort(), [
|
|
'created',
|
|
'existing',
|
|
]);
|
|
assert.deepEqual(
|
|
await current.completions.findByStartId(barrier.startId),
|
|
first.completion,
|
|
);
|
|
const storedArtifact = await current.completions.findResultArtifact(
|
|
artifact.artifactId,
|
|
);
|
|
assert.deepEqual(
|
|
openToolExecutionResultArtifact(
|
|
storedArtifact,
|
|
Buffer.alloc(32, 5),
|
|
registry,
|
|
),
|
|
result.output,
|
|
);
|
|
assert.equal(
|
|
current.client
|
|
.prepare(`SELECT COUNT(*) AS count FROM "ToolExecutionCompletions"`)
|
|
.get().count,
|
|
1,
|
|
);
|
|
const step = await current.stepRuns.findById('step-run-001');
|
|
assert.equal(step.status, 'succeeded');
|
|
assert.equal(step.outputRef, artifact.artifactId);
|
|
assert.deepEqual(
|
|
{
|
|
...current.client
|
|
.prepare(
|
|
`SELECT version, event_sequence AS "eventSequence"
|
|
FROM "Runs" WHERE id = 'run-001'`,
|
|
)
|
|
.get(),
|
|
},
|
|
{ version: 3, eventSequence: 3 },
|
|
);
|
|
const failure = createToolExecutionFailureResult(barrier, 'failed', 1_600);
|
|
const failedMutation = transitionStepRunMutation(
|
|
running,
|
|
{
|
|
expectedVersion: running.version,
|
|
expectedDigest: running.stepRunDigest,
|
|
mutationId: 'step-failed-after-success-004',
|
|
to: 'failed',
|
|
atMs: failure.completedAtMs,
|
|
...TOOL_EXECUTION_FAILURE_FACTS.failed,
|
|
},
|
|
{
|
|
expectedRunVersion: 2,
|
|
expectedRunEventSequence: 2,
|
|
eventId: '50000000-0000-4000-8000-000000000004',
|
|
dedupeKey: 'step-failed-after-success:step-run-001',
|
|
actor: REQUESTER,
|
|
},
|
|
);
|
|
await assert.rejects(
|
|
current.failureCompletions.commit(
|
|
createToolExecutionFailureCompletionCommand({
|
|
barrier,
|
|
failure,
|
|
stepRunMutation: failedMutation,
|
|
}),
|
|
),
|
|
ToolExecutionFailureCompletionConflictError,
|
|
);
|
|
} finally {
|
|
await current.close();
|
|
}
|
|
});
|
|
|
|
test('atomically records fixed failure facts and excludes a success outcome', async () => {
|
|
const current = await harness();
|
|
try {
|
|
const prepared = await current.starts.prepare(await command(current));
|
|
const barrier = prepared.barrier;
|
|
const running = await current.stepRuns.findById('step-run-001');
|
|
const failure = createToolExecutionFailureResult(barrier, 'failed', 1_500);
|
|
const failedMutation = transitionStepRunMutation(
|
|
running,
|
|
{
|
|
expectedVersion: running.version,
|
|
expectedDigest: running.stepRunDigest,
|
|
mutationId: 'step-failed-003',
|
|
to: 'failed',
|
|
atMs: failure.completedAtMs,
|
|
...TOOL_EXECUTION_FAILURE_FACTS.failed,
|
|
},
|
|
{
|
|
expectedRunVersion: 2,
|
|
expectedRunEventSequence: 2,
|
|
eventId: '50000000-0000-4000-8000-000000000003',
|
|
dedupeKey: 'step-failed:step-run-001',
|
|
actor: REQUESTER,
|
|
},
|
|
);
|
|
const failureCommand = createToolExecutionFailureCompletionCommand({
|
|
barrier,
|
|
failure,
|
|
stepRunMutation: failedMutation,
|
|
});
|
|
const [first, replay] = await Promise.all([
|
|
current.failureCompletions.commit(failureCommand),
|
|
current.failureCompletions.commit(failureCommand),
|
|
]);
|
|
assert.deepEqual([first.status, replay.status].sort(), [
|
|
'created',
|
|
'existing',
|
|
]);
|
|
assert.deepEqual(
|
|
await current.failureCompletions.findByStartId(barrier.startId),
|
|
first.completion,
|
|
);
|
|
const failedStep = await current.stepRuns.findById('step-run-001');
|
|
assert.equal(failedStep.status, 'failed');
|
|
assert.equal(failedStep.outputRef, null);
|
|
assert.equal(failedStep.resultCode, 'tool_adapter_failed');
|
|
assert.equal(failedStep.errorSummary, 'Trusted Tool execution failed');
|
|
assert.deepEqual(
|
|
{
|
|
...current.client
|
|
.prepare(
|
|
`SELECT
|
|
(SELECT COUNT(*) FROM "ToolExecutionFailureCompletions")
|
|
AS failureCount,
|
|
(SELECT COUNT(*) FROM "ToolExecutionCompletions")
|
|
AS successCount`,
|
|
)
|
|
.get(),
|
|
},
|
|
{ failureCount: 1, successCount: 0 },
|
|
);
|
|
|
|
const result = executionResult(
|
|
barrier,
|
|
{ summary: 'late success must lose' },
|
|
1_600,
|
|
);
|
|
const registry = projectToolDefinitionRegistry(projectToolSnapshot());
|
|
const artifact = createToolExecutionResultArtifact(
|
|
{
|
|
artifactId: 'artifact-result-late-001',
|
|
projectId: barrier.projectId,
|
|
runId: barrier.runId,
|
|
stepRunId: barrier.stepRunId,
|
|
tool: { name: 'demo.compare', version: '1.0.0' },
|
|
executionResult: result,
|
|
keyId: 'tool-result-key-test',
|
|
key: Buffer.alloc(32, 5),
|
|
},
|
|
registry,
|
|
() => Buffer.alloc(12, 4),
|
|
);
|
|
const succeededMutation = transitionStepRunMutation(
|
|
running,
|
|
{
|
|
expectedVersion: running.version,
|
|
expectedDigest: running.stepRunDigest,
|
|
mutationId: 'step-succeeded-after-failure-004',
|
|
to: 'succeeded',
|
|
atMs: result.completedAtMs,
|
|
outputRef: artifact.artifactId,
|
|
},
|
|
{
|
|
expectedRunVersion: 2,
|
|
expectedRunEventSequence: 2,
|
|
eventId: '50000000-0000-4000-8000-000000000004',
|
|
dedupeKey: 'step-succeeded-after-failure:step-run-001',
|
|
actor: REQUESTER,
|
|
},
|
|
);
|
|
await assert.rejects(
|
|
current.completions.commit(
|
|
createToolExecutionCompletionCommand({
|
|
barrier,
|
|
executionResult: result,
|
|
resultArtifact: artifact,
|
|
resultKeyCatalogFence: current.resultKeyCatalogFence,
|
|
stepRunMutation: succeededMutation,
|
|
}),
|
|
),
|
|
ToolExecutionCompletionConflictError,
|
|
);
|
|
} finally {
|
|
await current.close();
|
|
}
|
|
});
|
|
|
|
test('rejects replay identity drift without changing durable state', async () => {
|
|
const current = await harness();
|
|
try {
|
|
const first = await command(current);
|
|
const drift = await command(current, {
|
|
startId: 'tool-start-other',
|
|
});
|
|
await current.starts.prepare(first);
|
|
await assert.rejects(
|
|
current.starts.prepare(drift),
|
|
ToolExecutionStartBarrierConflictError,
|
|
);
|
|
assert.deepEqual(counts(current.client), {
|
|
barriers: 1,
|
|
bindings: 1,
|
|
traces: 1,
|
|
receipts: 1,
|
|
audits: 1,
|
|
});
|
|
} finally {
|
|
await current.close();
|
|
}
|
|
});
|
|
|
|
test('rolls back every fact when an audit identity already exists', async () => {
|
|
const current = await harness();
|
|
try {
|
|
const start = await command(current);
|
|
const audit = start.evidence.audit;
|
|
current.client
|
|
.prepare(
|
|
`INSERT INTO "QingLong3SecurityAuditEvents" (
|
|
event_id, request_id, operation_id, project_id, subject_type,
|
|
subject_id, authentication_id, outcome, reasons_json,
|
|
fence_project_version, fence_binding_version, occurred_at_ms
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
)
|
|
.run(
|
|
audit.eventId,
|
|
audit.requestId,
|
|
audit.operationId,
|
|
audit.projectId,
|
|
audit.subject.type,
|
|
audit.subject.id,
|
|
audit.authenticationId,
|
|
audit.outcome,
|
|
JSON.stringify(audit.reasons),
|
|
audit.fence.projectVersion,
|
|
audit.fence.bindingVersion,
|
|
audit.occurredAtMs,
|
|
);
|
|
await assert.rejects(
|
|
current.starts.prepare(start),
|
|
ToolExecutionStartBarrierConflictError,
|
|
);
|
|
assert.deepEqual(counts(current.client), {
|
|
barriers: 0,
|
|
bindings: 0,
|
|
traces: 0,
|
|
receipts: 0,
|
|
audits: 1,
|
|
});
|
|
assert.equal(
|
|
(await current.stepRuns.findById('step-run-001')).status,
|
|
'ready',
|
|
);
|
|
assert.equal(
|
|
current.client
|
|
.prepare(`SELECT version FROM "Runs" WHERE id = 'run-001'`)
|
|
.get().version,
|
|
1,
|
|
);
|
|
} finally {
|
|
await current.close();
|
|
}
|
|
});
|
|
|
|
test('fails closed when a stored barrier projection is corrupted', async () => {
|
|
const current = await harness();
|
|
try {
|
|
const start = await command(current);
|
|
await current.starts.prepare(start);
|
|
current.client.exec('PRAGMA ignore_check_constraints = ON');
|
|
current.client
|
|
.prepare(
|
|
`UPDATE "ToolExecutionStartBarriers"
|
|
SET barrier_json = json_set(
|
|
barrier_json, '$.startedAtMs', 1401
|
|
) WHERE start_id = 'tool-start-001'`,
|
|
)
|
|
.run();
|
|
await assert.rejects(
|
|
current.starts.findByStartId('tool-start-001'),
|
|
ToolExecutionStartBarrierUnavailableError,
|
|
);
|
|
} finally {
|
|
await current.close();
|
|
}
|
|
});
|
|
|
|
test('fails closed when a historical barrier has no Artifact binding', async () => {
|
|
const current = await harness();
|
|
try {
|
|
const start = await command(current);
|
|
await current.starts.prepare(start);
|
|
current.client
|
|
.prepare(
|
|
`DELETE FROM "ToolExecutionStartArtifactBindings"
|
|
WHERE start_id = 'tool-start-001'`,
|
|
)
|
|
.run();
|
|
await assert.rejects(
|
|
current.starts.findByStartId('tool-start-001'),
|
|
ToolExecutionStartBarrierUnavailableError,
|
|
);
|
|
} finally {
|
|
await current.close();
|
|
}
|
|
});
|
|
|
|
test('rejects a new Tool start pinned to a quarantined Package lock', async () => {
|
|
const current = await harness();
|
|
try {
|
|
const start = await command(current);
|
|
const snapshot = projectToolSnapshot();
|
|
const source = snapshot.sources[0];
|
|
const quarantine = createPluginPackageQuarantineEvent({
|
|
mutationId: 'quarantine-tool-demo',
|
|
revocationReceiptDigest: DIGEST_B,
|
|
impactDigest: DIGEST_C,
|
|
target: {
|
|
projectId: snapshot.projectId,
|
|
packageName: source.packageName,
|
|
installationId: source.installationId,
|
|
lockDigest: source.lockDigest,
|
|
installState: 'active',
|
|
installVersion: 1,
|
|
installRecordDigest: 'd'.repeat(64),
|
|
activeLockDigest: source.lockDigest,
|
|
},
|
|
proposer: { type: 'user', id: 'owner-a' },
|
|
confirmer: { type: 'user', id: 'owner-b' },
|
|
authorizationMode: 'dual_control',
|
|
reasonCode: 'confirmed_key_compromise',
|
|
occurredAtMs: 1_300,
|
|
});
|
|
current.client.exec('PRAGMA foreign_keys = OFF');
|
|
current.client
|
|
.prepare(
|
|
`INSERT INTO "QingLong3ProjectToolDefinitionSnapshots" (
|
|
project_id, active_vector_digest, definitions_digest,
|
|
snapshot_digest, snapshot_json, committed_at_ms
|
|
) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
)
|
|
.run(
|
|
snapshot.projectId,
|
|
snapshot.activeVectorDigest,
|
|
snapshot.definitionsDigest,
|
|
snapshot.snapshotDigest,
|
|
JSON.stringify(snapshot),
|
|
1_200,
|
|
);
|
|
current.client
|
|
.prepare(
|
|
`INSERT INTO "QingLong3ProjectToolDefinitionSnapshotSources" (
|
|
project_id, active_vector_digest, package_name, installation_id,
|
|
generation, generation_digest, lock_digest, revision_digest
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
)
|
|
.run(
|
|
snapshot.projectId,
|
|
snapshot.activeVectorDigest,
|
|
source.packageName,
|
|
source.installationId,
|
|
source.generation,
|
|
source.generationDigest,
|
|
source.lockDigest,
|
|
source.revisionDigest,
|
|
);
|
|
current.client
|
|
.prepare(
|
|
`INSERT INTO "QingLong3PluginPackageQuarantineEvents" (
|
|
event_digest, mutation_id, revocation_receipt_digest, impact_digest,
|
|
project_id, package_name, installation_id, lock_digest,
|
|
install_state, install_version, install_record_digest,
|
|
active_lock_digest, proposer_type, proposer_id, confirmer_type,
|
|
confirmer_id, authorization_mode, reason_code, occurred_at_ms,
|
|
event_json
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
)
|
|
.run(
|
|
quarantine.eventDigest,
|
|
quarantine.mutationId,
|
|
quarantine.revocationReceiptDigest,
|
|
quarantine.impactDigest,
|
|
quarantine.target.projectId,
|
|
quarantine.target.packageName,
|
|
quarantine.target.installationId,
|
|
quarantine.target.lockDigest,
|
|
quarantine.target.installState,
|
|
quarantine.target.installVersion,
|
|
quarantine.target.installRecordDigest,
|
|
quarantine.target.activeLockDigest,
|
|
quarantine.proposer.type,
|
|
quarantine.proposer.id,
|
|
quarantine.confirmer.type,
|
|
quarantine.confirmer.id,
|
|
quarantine.authorizationMode,
|
|
quarantine.reasonCode,
|
|
quarantine.occurredAtMs,
|
|
JSON.stringify(quarantine),
|
|
);
|
|
const before = counts(current.client);
|
|
await assert.rejects(
|
|
current.starts.prepare(start),
|
|
ToolExecutionStartBarrierConflictError,
|
|
);
|
|
assert.deepEqual(counts(current.client), before);
|
|
} finally {
|
|
await current.close();
|
|
}
|
|
});
|
|
|
|
test('rejects a Package Tool while lifecycle is disabled and admits it after enable', async () => {
|
|
const current = await harness();
|
|
try {
|
|
const start = await command(current);
|
|
const snapshot = projectToolSnapshot();
|
|
const source = snapshot.sources[0];
|
|
current.client.exec('PRAGMA foreign_keys = OFF');
|
|
current.client
|
|
.prepare(
|
|
`INSERT INTO "QingLong3ProjectToolDefinitionSnapshots" (
|
|
project_id, active_vector_digest, definitions_digest,
|
|
snapshot_digest, snapshot_json, committed_at_ms
|
|
) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
)
|
|
.run(
|
|
snapshot.projectId,
|
|
snapshot.activeVectorDigest,
|
|
snapshot.definitionsDigest,
|
|
snapshot.snapshotDigest,
|
|
JSON.stringify(snapshot),
|
|
1_200,
|
|
);
|
|
current.client
|
|
.prepare(
|
|
`INSERT INTO "QingLong3ProjectToolDefinitionSnapshotSources" (
|
|
project_id, active_vector_digest, package_name, installation_id,
|
|
generation, generation_digest, lock_digest, revision_digest
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
)
|
|
.run(
|
|
snapshot.projectId,
|
|
snapshot.activeVectorDigest,
|
|
source.packageName,
|
|
source.installationId,
|
|
source.generation,
|
|
source.generationDigest,
|
|
source.lockDigest,
|
|
source.revisionDigest,
|
|
);
|
|
current.client
|
|
.prepare(
|
|
`INSERT INTO "QingLong3PluginPackageLifecycleHeads" (
|
|
project_id, package_name, installation_id, lock_digest,
|
|
install_record_digest, version, disposition, event_digest,
|
|
updated_at_ms
|
|
) VALUES (?, ?, ?, ?, ?, 1, 'disabled', ?, 1300)`,
|
|
)
|
|
.run(
|
|
snapshot.projectId,
|
|
source.packageName,
|
|
source.installationId,
|
|
source.lockDigest,
|
|
'd'.repeat(64),
|
|
'e'.repeat(64),
|
|
);
|
|
current.client.exec('PRAGMA foreign_keys = ON');
|
|
|
|
const before = counts(current.client);
|
|
await assert.rejects(
|
|
current.starts.prepare(start),
|
|
ToolExecutionStartBarrierConflictError,
|
|
);
|
|
assert.deepEqual(counts(current.client), before);
|
|
|
|
current.client
|
|
.prepare(
|
|
`UPDATE "QingLong3PluginPackageLifecycleHeads"
|
|
SET disposition = 'active', version = 2, updated_at_ms = 1301
|
|
WHERE project_id = ? AND package_name = ?`,
|
|
)
|
|
.run(snapshot.projectId, source.packageName);
|
|
const prepared = await current.starts.prepare(start);
|
|
assert.equal(prepared.status, 'created');
|
|
} finally {
|
|
await current.close();
|
|
}
|
|
});
|
|
|
|
test('publishes only the explicit adapter subpath', () => {
|
|
const root = require('../dist');
|
|
const runtime = require('@qinglong/local-sqlite/runtime');
|
|
const subpath = require('@qinglong/local-sqlite/tool-execution-start-barrier');
|
|
const completion = require('@qinglong/local-sqlite/tool-execution-completion');
|
|
const failureCompletion = require('@qinglong/local-sqlite/tool-execution-failure-completion');
|
|
assert.equal(root.LocalSqliteToolExecutionStartBarrierRepository, undefined);
|
|
assert.equal(root.LocalSqliteToolExecutionCompletionRepository, undefined);
|
|
assert.equal(
|
|
root.LocalSqliteToolExecutionFailureCompletionRepository,
|
|
undefined,
|
|
);
|
|
assert.equal(
|
|
runtime.LocalSqliteToolExecutionStartBarrierRepository,
|
|
undefined,
|
|
);
|
|
assert.equal(runtime.LocalSqliteToolExecutionCompletionRepository, undefined);
|
|
assert.equal(
|
|
runtime.LocalSqliteToolExecutionFailureCompletionRepository,
|
|
undefined,
|
|
);
|
|
assert.equal(
|
|
subpath.LocalSqliteToolExecutionStartBarrierRepository,
|
|
LocalSqliteToolExecutionStartBarrierRepository,
|
|
);
|
|
assert.equal(
|
|
completion.LocalSqliteToolExecutionCompletionRepository,
|
|
LocalSqliteToolExecutionCompletionRepository,
|
|
);
|
|
assert.equal(
|
|
failureCompletion.LocalSqliteToolExecutionFailureCompletionRepository,
|
|
LocalSqliteToolExecutionFailureCompletionRepository,
|
|
);
|
|
});
|