mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 02:27:44 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,475 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
|
||||
import type { RunRepositoryReader } from '../run/runRepository';
|
||||
import {
|
||||
normalizeStepRunRecord,
|
||||
transitionStepRunMutation,
|
||||
type StepRunRepository,
|
||||
} from '../run/stepRun';
|
||||
import {
|
||||
createToolExecutionCompletionCommand,
|
||||
createToolExecutionResultArtifact,
|
||||
normalizeToolExecutionCompletionRecord,
|
||||
normalizeToolExecutionResultArtifact,
|
||||
openToolExecutionResultArtifact,
|
||||
ToolExecutionCompletionConflictError,
|
||||
ToolExecutionCompletionUnavailableError,
|
||||
toolExecutionCompletionRecord,
|
||||
type ToolExecutionCompletionRecord,
|
||||
type ToolExecutionCompletionRepository,
|
||||
type ToolExecutionResultArtifact,
|
||||
} from './toolExecutionCompletion';
|
||||
import {
|
||||
normalizeToolExecutionStartBarrierRecord,
|
||||
type ToolExecutionStartBarrierRecord,
|
||||
} from './toolExecutionStartBarrier';
|
||||
import {
|
||||
normalizeToolResultKeyCatalogRecord,
|
||||
requireActiveToolResultKey,
|
||||
requireDecryptableToolResultKey,
|
||||
toolResultKeyCatalogFence,
|
||||
toolResultKeyMaterialProof,
|
||||
type ToolResultKeyCatalogReader,
|
||||
type ToolResultKeyCatalogRecord,
|
||||
} from './toolResultKeyCatalog';
|
||||
import type {
|
||||
ToolInvocationArtifactKeyMaterial,
|
||||
ToolInvocationArtifactKeyProvider,
|
||||
} from './toolInvocationArtifact';
|
||||
import {
|
||||
normalizeToolExecutionResultRekeyOverlay,
|
||||
openToolExecutionResultRekeyOverlay,
|
||||
type ToolExecutionResultRekeyOverlay,
|
||||
type ToolExecutionResultRekeyReader,
|
||||
} from './toolResultRekey';
|
||||
import {
|
||||
executeTrustedToolAfterStart,
|
||||
TrustedToolExecutionAdapterRegistry,
|
||||
type TrustedToolExecutionDependencies,
|
||||
} from './trustedToolExecution';
|
||||
import type { ToolJsonValue } from './tool-registry/toolRegistry';
|
||||
|
||||
export interface TrustedToolSuccessCompletionIdentities {
|
||||
readonly artifactId: string;
|
||||
readonly mutationId: string;
|
||||
readonly eventId: string;
|
||||
}
|
||||
|
||||
export interface TrustedToolSuccessCompletionIdentityFactory {
|
||||
create(startId: string): TrustedToolSuccessCompletionIdentities;
|
||||
}
|
||||
|
||||
export interface TrustedToolSuccessCompletionDependencies
|
||||
extends TrustedToolExecutionDependencies {
|
||||
readonly completions: ToolExecutionCompletionRepository;
|
||||
readonly stepRuns: Pick<StepRunRepository, 'findById'>;
|
||||
readonly runs: Pick<RunRepositoryReader, 'findRunById'>;
|
||||
readonly resultKeyCatalog: ToolResultKeyCatalogReader;
|
||||
readonly resultRekeys: ToolExecutionResultRekeyReader;
|
||||
readonly resultKeys: Pick<ToolInvocationArtifactKeyProvider, 'resolve'>;
|
||||
readonly identities: TrustedToolSuccessCompletionIdentityFactory;
|
||||
readonly nonceFactory?: () => Uint8Array;
|
||||
}
|
||||
|
||||
export interface TrustedToolSuccessCompletionResult {
|
||||
readonly status: 'created' | 'existing';
|
||||
readonly completion: Readonly<ToolExecutionCompletionRecord>;
|
||||
readonly output: ToolJsonValue;
|
||||
}
|
||||
|
||||
const TERMINAL_RUN_STATUSES = new Set([
|
||||
'succeeded',
|
||||
'failed',
|
||||
'cancelled',
|
||||
'timed_out',
|
||||
]);
|
||||
|
||||
function unavailable(cause?: unknown): never {
|
||||
throw new ToolExecutionCompletionUnavailableError({
|
||||
cause: cause instanceof Error ? cause : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
function conflict(): never {
|
||||
throw new ToolExecutionCompletionConflictError();
|
||||
}
|
||||
|
||||
function sameValue(left: unknown, right: unknown): boolean {
|
||||
return JSON.stringify(left) === JSON.stringify(right);
|
||||
}
|
||||
|
||||
function validateDependencies(
|
||||
dependencies: TrustedToolSuccessCompletionDependencies,
|
||||
): void {
|
||||
if (
|
||||
!dependencies ||
|
||||
typeof dependencies !== 'object' ||
|
||||
!dependencies.completions ||
|
||||
typeof dependencies.completions.findByStartId !== 'function' ||
|
||||
typeof dependencies.completions.findResultArtifact !== 'function' ||
|
||||
typeof dependencies.completions.commit !== 'function' ||
|
||||
!dependencies.stepRuns ||
|
||||
typeof dependencies.stepRuns.findById !== 'function' ||
|
||||
!dependencies.runs ||
|
||||
typeof dependencies.runs.findRunById !== 'function' ||
|
||||
!dependencies.resultKeys ||
|
||||
typeof dependencies.resultKeys.resolve !== 'function' ||
|
||||
!dependencies.resultKeyCatalog ||
|
||||
typeof dependencies.resultKeyCatalog.findCurrent !== 'function' ||
|
||||
!dependencies.resultRekeys ||
|
||||
typeof dependencies.resultRekeys.findHeadByArtifactId !== 'function' ||
|
||||
!dependencies.identities ||
|
||||
typeof dependencies.identities.create !== 'function' ||
|
||||
!(dependencies.adapters instanceof TrustedToolExecutionAdapterRegistry) ||
|
||||
(dependencies.nonceFactory !== undefined &&
|
||||
typeof dependencies.nonceFactory !== 'function')
|
||||
) {
|
||||
unavailable();
|
||||
}
|
||||
}
|
||||
|
||||
async function findResultRekeyHead(
|
||||
artifactId: string,
|
||||
dependencies: TrustedToolSuccessCompletionDependencies,
|
||||
): Promise<Readonly<ToolExecutionResultRekeyOverlay> | null> {
|
||||
try {
|
||||
const value = await dependencies.resultRekeys.findHeadByArtifactId(
|
||||
artifactId,
|
||||
);
|
||||
return value === null
|
||||
? null
|
||||
: normalizeToolExecutionResultRekeyOverlay(value);
|
||||
} catch (cause) {
|
||||
return unavailable(cause);
|
||||
}
|
||||
}
|
||||
|
||||
async function findResultKeyCatalog(
|
||||
dependencies: TrustedToolSuccessCompletionDependencies,
|
||||
): Promise<Readonly<ToolResultKeyCatalogRecord>> {
|
||||
try {
|
||||
const value = await dependencies.resultKeyCatalog.findCurrent();
|
||||
if (!value) return unavailable();
|
||||
return normalizeToolResultKeyCatalogRecord(value);
|
||||
} catch (cause) {
|
||||
return unavailable(cause);
|
||||
}
|
||||
}
|
||||
|
||||
function validCatalogMaterial(
|
||||
material: ToolInvocationArtifactKeyMaterial | null,
|
||||
keyId: string,
|
||||
materialProof: string,
|
||||
): material is ToolInvocationArtifactKeyMaterial {
|
||||
return (
|
||||
validKey(material, keyId) &&
|
||||
toolResultKeyMaterialProof(keyId, material.key) === materialProof
|
||||
);
|
||||
}
|
||||
|
||||
async function findCompletion(
|
||||
startId: string,
|
||||
dependencies: TrustedToolSuccessCompletionDependencies,
|
||||
): Promise<Readonly<ToolExecutionCompletionRecord> | null> {
|
||||
try {
|
||||
const value = await dependencies.completions.findByStartId(startId);
|
||||
return value === null
|
||||
? null
|
||||
: normalizeToolExecutionCompletionRecord(value);
|
||||
} catch (cause) {
|
||||
return unavailable(cause);
|
||||
}
|
||||
}
|
||||
|
||||
async function findBarrier(
|
||||
startId: string,
|
||||
dependencies: TrustedToolSuccessCompletionDependencies,
|
||||
): Promise<Readonly<ToolExecutionStartBarrierRecord>> {
|
||||
try {
|
||||
const value = await dependencies.barriers.findByStartId(startId);
|
||||
if (!value) return unavailable();
|
||||
const barrier = normalizeToolExecutionStartBarrierRecord(value);
|
||||
if (barrier.startId !== startId) return unavailable();
|
||||
return barrier;
|
||||
} catch (cause) {
|
||||
return unavailable(cause);
|
||||
}
|
||||
}
|
||||
|
||||
function validKey(
|
||||
material: ToolInvocationArtifactKeyMaterial | null,
|
||||
expectedKeyId?: string,
|
||||
): material is ToolInvocationArtifactKeyMaterial {
|
||||
return (
|
||||
material !== null &&
|
||||
typeof material.keyId === 'string' &&
|
||||
(expectedKeyId === undefined || material.keyId === expectedKeyId) &&
|
||||
material.key instanceof Uint8Array &&
|
||||
material.key.byteLength === 32
|
||||
);
|
||||
}
|
||||
|
||||
function wipeMaterial(
|
||||
material: ToolInvocationArtifactKeyMaterial | null | undefined,
|
||||
): void {
|
||||
if (material?.key instanceof Uint8Array) material.key.fill(0);
|
||||
}
|
||||
|
||||
function completionMatches(
|
||||
completion: Readonly<ToolExecutionCompletionRecord>,
|
||||
barrier: Readonly<ToolExecutionStartBarrierRecord>,
|
||||
artifact: Readonly<ToolExecutionResultArtifact>,
|
||||
): boolean {
|
||||
return (
|
||||
completion.startId === barrier.startId &&
|
||||
completion.projectId === barrier.projectId &&
|
||||
completion.runId === barrier.runId &&
|
||||
completion.stepRunId === barrier.stepRunId &&
|
||||
completion.startedStepRunVersion === barrier.startedStepRunVersion &&
|
||||
completion.barrierDigest === barrier.barrierDigest &&
|
||||
completion.adapterDigest === barrier.adapterDigest &&
|
||||
artifact.artifactId === completion.resultArtifact.artifactId &&
|
||||
artifact.artifactDigest === completion.resultArtifact.artifactDigest &&
|
||||
artifact.projectId === completion.projectId &&
|
||||
artifact.startId === completion.startId &&
|
||||
artifact.runId === completion.runId &&
|
||||
artifact.stepRunId === completion.stepRunId &&
|
||||
artifact.barrierDigest === completion.barrierDigest &&
|
||||
artifact.adapterDigest === completion.adapterDigest &&
|
||||
artifact.outputDigest === completion.resultArtifact.outputDigest &&
|
||||
artifact.executionResultDigest ===
|
||||
completion.resultArtifact.executionResultDigest &&
|
||||
artifact.sealedAtMs === completion.completedAtMs
|
||||
);
|
||||
}
|
||||
|
||||
async function openDurableCompletion(
|
||||
completion: Readonly<ToolExecutionCompletionRecord>,
|
||||
dependencies: TrustedToolSuccessCompletionDependencies,
|
||||
): Promise<Readonly<TrustedToolSuccessCompletionResult>> {
|
||||
const barrier = await findBarrier(completion.startId, dependencies);
|
||||
let artifact: Readonly<ToolExecutionResultArtifact>;
|
||||
try {
|
||||
const value = await dependencies.completions.findResultArtifact(
|
||||
completion.resultArtifact.artifactId,
|
||||
);
|
||||
if (!value) return unavailable();
|
||||
artifact = normalizeToolExecutionResultArtifact(value);
|
||||
} catch (cause) {
|
||||
return unavailable(cause);
|
||||
}
|
||||
if (!completionMatches(completion, barrier, artifact)) {
|
||||
return conflict();
|
||||
}
|
||||
|
||||
const adapter = dependencies.adapters.resolve(barrier);
|
||||
if (!sameValue(artifact.tool, adapter.binding.tool)) {
|
||||
return conflict();
|
||||
}
|
||||
|
||||
let material: ToolInvocationArtifactKeyMaterial | null | undefined;
|
||||
try {
|
||||
const catalog = await findResultKeyCatalog(dependencies);
|
||||
const overlay = await findResultRekeyHead(
|
||||
artifact.artifactId,
|
||||
dependencies,
|
||||
);
|
||||
const keyId = overlay?.targetCatalogFence.keyId ?? artifact.keyId;
|
||||
const entry = requireDecryptableToolResultKey(catalog, keyId);
|
||||
const materialProof =
|
||||
overlay?.targetCatalogFence.materialProof ?? entry.materialProof;
|
||||
if (entry.materialProof !== materialProof) return unavailable();
|
||||
material = await dependencies.resultKeys.resolve(keyId);
|
||||
if (!validCatalogMaterial(material, keyId, materialProof)) {
|
||||
return unavailable();
|
||||
}
|
||||
const output = overlay
|
||||
? openToolExecutionResultRekeyOverlay(
|
||||
overlay,
|
||||
material.key,
|
||||
dependencies.adapters.definitionRegistry(),
|
||||
artifact,
|
||||
)
|
||||
: openToolExecutionResultArtifact(
|
||||
artifact,
|
||||
material.key,
|
||||
dependencies.adapters.definitionRegistry(),
|
||||
);
|
||||
return Object.freeze({
|
||||
status: 'existing' as const,
|
||||
completion,
|
||||
output,
|
||||
});
|
||||
} catch (cause) {
|
||||
return unavailable(cause);
|
||||
} finally {
|
||||
wipeMaterial(material);
|
||||
}
|
||||
}
|
||||
|
||||
async function findStepRun(
|
||||
barrier: Readonly<ToolExecutionStartBarrierRecord>,
|
||||
dependencies: TrustedToolSuccessCompletionDependencies,
|
||||
) {
|
||||
try {
|
||||
const value = await dependencies.stepRuns.findById(barrier.stepRunId);
|
||||
if (!value) return unavailable();
|
||||
return normalizeStepRunRecord(value);
|
||||
} catch (cause) {
|
||||
return unavailable(cause);
|
||||
}
|
||||
}
|
||||
|
||||
async function findRun(
|
||||
barrier: Readonly<ToolExecutionStartBarrierRecord>,
|
||||
dependencies: TrustedToolSuccessCompletionDependencies,
|
||||
) {
|
||||
try {
|
||||
const value = await dependencies.runs.findRunById(barrier.runId);
|
||||
if (!value) return unavailable();
|
||||
return value;
|
||||
} catch (cause) {
|
||||
return unavailable(cause);
|
||||
}
|
||||
}
|
||||
|
||||
function completionDedupeKey(startId: string): string {
|
||||
return `tool-success:${createHash('sha256').update(startId).digest('hex')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes one already-started retry-safe Tool and closes its success path.
|
||||
*
|
||||
* Durable completion is checked before adapter execution and again after it.
|
||||
* If the commit response is lost, the stored encrypted result is reopened and
|
||||
* returned without executing the adapter a second time in this call.
|
||||
*/
|
||||
export async function executeAndCompleteTrustedToolSuccess(
|
||||
startId: string,
|
||||
dependencies: TrustedToolSuccessCompletionDependencies,
|
||||
): Promise<Readonly<TrustedToolSuccessCompletionResult>> {
|
||||
validateDependencies(dependencies);
|
||||
|
||||
const existing = await findCompletion(startId, dependencies);
|
||||
if (existing) return openDurableCompletion(existing, dependencies);
|
||||
|
||||
const executionResult = await executeTrustedToolAfterStart(
|
||||
startId,
|
||||
dependencies,
|
||||
);
|
||||
|
||||
const concurrent = await findCompletion(startId, dependencies);
|
||||
if (concurrent) return openDurableCompletion(concurrent, dependencies);
|
||||
|
||||
const barrier = await findBarrier(startId, dependencies);
|
||||
const adapter = dependencies.adapters.resolve(barrier);
|
||||
const stepRun = await findStepRun(barrier, dependencies);
|
||||
const run = await findRun(barrier, dependencies);
|
||||
if (
|
||||
stepRun.id !== barrier.stepRunId ||
|
||||
stepRun.runId !== barrier.runId ||
|
||||
stepRun.kind !== 'tool' ||
|
||||
stepRun.status !== 'running' ||
|
||||
stepRun.version !== barrier.startedStepRunVersion ||
|
||||
stepRun.stepRunDigest !== barrier.startedStepRunDigest ||
|
||||
run.id !== barrier.runId ||
|
||||
run.projectId !== barrier.projectId ||
|
||||
!Number.isSafeInteger(run.version) ||
|
||||
run.version < 0 ||
|
||||
!Number.isSafeInteger(run.eventSequence) ||
|
||||
run.eventSequence < 0 ||
|
||||
TERMINAL_RUN_STATUSES.has(run.status)
|
||||
) {
|
||||
return conflict();
|
||||
}
|
||||
|
||||
let identities: TrustedToolSuccessCompletionIdentities;
|
||||
try {
|
||||
identities = dependencies.identities.create(startId);
|
||||
} catch (cause) {
|
||||
return unavailable(cause);
|
||||
}
|
||||
|
||||
let material: ToolInvocationArtifactKeyMaterial | null | undefined;
|
||||
let resultArtifact: Readonly<ToolExecutionResultArtifact>;
|
||||
let resultKeyCatalogFence: ReturnType<typeof toolResultKeyCatalogFence>;
|
||||
try {
|
||||
const catalog = await findResultKeyCatalog(dependencies);
|
||||
const entry = requireActiveToolResultKey(catalog);
|
||||
material = await dependencies.resultKeys.resolve(entry.keyId);
|
||||
if (!validCatalogMaterial(material, entry.keyId, entry.materialProof)) {
|
||||
return unavailable();
|
||||
}
|
||||
resultKeyCatalogFence = toolResultKeyCatalogFence(catalog, entry);
|
||||
resultArtifact = createToolExecutionResultArtifact(
|
||||
{
|
||||
artifactId: identities.artifactId,
|
||||
projectId: barrier.projectId,
|
||||
runId: barrier.runId,
|
||||
stepRunId: barrier.stepRunId,
|
||||
tool: adapter.binding.tool,
|
||||
executionResult,
|
||||
keyId: entry.keyId,
|
||||
key: material.key,
|
||||
},
|
||||
dependencies.adapters.definitionRegistry(),
|
||||
dependencies.nonceFactory,
|
||||
);
|
||||
} catch (cause) {
|
||||
return unavailable(cause);
|
||||
} finally {
|
||||
wipeMaterial(material);
|
||||
}
|
||||
|
||||
const stepRunMutation = transitionStepRunMutation(
|
||||
stepRun,
|
||||
{
|
||||
expectedVersion: stepRun.version,
|
||||
expectedDigest: stepRun.stepRunDigest,
|
||||
mutationId: identities.mutationId,
|
||||
to: 'succeeded',
|
||||
atMs: executionResult.completedAtMs,
|
||||
outputRef: resultArtifact.artifactId,
|
||||
},
|
||||
{
|
||||
expectedRunVersion: run.version,
|
||||
expectedRunEventSequence: run.eventSequence,
|
||||
eventId: identities.eventId,
|
||||
dedupeKey: completionDedupeKey(startId),
|
||||
actor: Object.freeze({
|
||||
type: 'system' as const,
|
||||
id: 'trusted-tool-runtime',
|
||||
}),
|
||||
},
|
||||
);
|
||||
const command = createToolExecutionCompletionCommand({
|
||||
barrier,
|
||||
executionResult,
|
||||
resultArtifact,
|
||||
resultKeyCatalogFence,
|
||||
stepRunMutation,
|
||||
});
|
||||
const expectedCompletion = toolExecutionCompletionRecord(command);
|
||||
|
||||
try {
|
||||
const committed = await dependencies.completions.commit(command);
|
||||
const completion = normalizeToolExecutionCompletionRecord(
|
||||
committed.completion,
|
||||
);
|
||||
if (
|
||||
!['created', 'existing'].includes(committed.status) ||
|
||||
!sameValue(completion, expectedCompletion)
|
||||
) {
|
||||
return conflict();
|
||||
}
|
||||
return Object.freeze({
|
||||
status: committed.status,
|
||||
completion,
|
||||
output: executionResult.output,
|
||||
});
|
||||
} catch (cause) {
|
||||
const recovered = await findCompletion(startId, dependencies);
|
||||
if (recovered) return openDurableCompletion(recovered, dependencies);
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user