Files
qinglong/scripts/ql3-postgres-ha-tool-result-fixture.cjs

831 lines
26 KiB
JavaScript

const assert = require('node:assert/strict');
const {
PostgresRunRepository,
PostgresStepRunRepository,
PostgresToolExecutionCompletionRepository,
PostgresToolExecutionStartBarrierRepository,
PostgresToolInvocationArtifactRepository,
PostgresToolResultKeyCatalogReader,
PostgresToolResultRekeyReader,
} = require('../packages/ql3-cluster-postgres/dist/entrypoints/runtime.js');
const {
PostgresToolResultKeyCatalogRepository,
PostgresToolResultRekeyRepository,
} = require('../packages/ql3-cluster-postgres/dist/entrypoints/admin.js');
const {
createStepRunMutation,
transitionStepRunMutation,
} = require('../packages/ql3-runtime-core/dist/run/stepRun.js');
const {
TOOL_EXECUTION_START_AUDIT_OPERATION,
createToolExecutionEvidenceBundle,
toolExecutionAdmissionEvidence,
} = require('../packages/ql3-runtime-core/dist/tool-execution/toolExecutionEvidence.js');
const {
createPluginPackageResourceGenerationFromReferences,
} = require('../packages/ql3-runtime-core/dist/plugin-package/pluginPackageResourceGeneration.js');
const {
createProjectToolDefinitionSnapshot,
projectToolDefinitionRegistry,
} = require('../packages/ql3-runtime-core/dist/tool-execution/tool-registry/projectToolDefinitionSnapshot.js');
const {
TrustedToolHandlerBindingRegistry,
admitTrustedToolExecution,
createTrustedToolHandlerBinding,
createTrustedToolInvocationPlan,
trustedToolContractIdentityDigest,
} = require('../packages/ql3-runtime-core/dist/tool-execution/trustedToolInvocation.js');
const {
prepareToolInvocation,
} = require('../packages/ql3-runtime-core/dist/tool-execution/tool-registry/toolRegistry.js');
const {
createToolExecutionStartCommand,
} = require('../packages/ql3-runtime-core/dist/tool-execution/toolExecutionStartBarrier.js');
const {
TOOL_EXECUTION_RESULT_KEY_BINDING_SCHEMA,
normalizeToolExecutionResultKeyBinding,
} = require('../packages/ql3-runtime-core/dist/tool-execution/toolExecutionCompletion.js');
const {
createToolResultKeyCatalogBootstrapCommand,
createToolResultKeyRetirementCommand,
createToolResultKeyRotationCommand,
requireActiveToolResultKey,
toolResultKeyCatalogFence,
toolResultKeyMaterialProof,
} = require('../packages/ql3-runtime-core/dist/tool-execution/toolResultKeyCatalog.js');
const {
createToolExecutionResultRekeyCommand,
createToolResultKeyRetirementReceiptCommand,
} = require('../packages/ql3-runtime-core/dist/tool-execution/toolResultRekey.js');
const {
TrustedToolExecutionAdapterRegistry,
} = require('../packages/ql3-runtime-core/dist/tool-execution/trustedToolExecution.js');
const {
executeAndCompleteTrustedToolSuccess,
} = require('../packages/ql3-runtime-core/dist/tool-execution/trustedToolSuccessCompletion.js');
const PROJECT_ID = 'ha-tool-result-project';
const RUN_ID = '34000000-0000-4000-8000-000000000001';
const STEP_RUN_ID = 'ha-tool-result-step';
const START_ID = 'ha-tool-result-start';
const TOOL = Object.freeze({
name: 'ha.result.read',
version: '1.0.0',
});
const SUBJECT = Object.freeze({
type: 'user',
id: 'usr-ha-tool-result',
});
const POLICY_FENCE = Object.freeze({
projectVersion: 1,
bindingVersion: 1,
});
const INVOCATION_KEY_ID = 'ha-tool-invocation-key';
const RESULT_KEY_A_ID = 'ha-result-key-a';
const RESULT_KEY_B_ID = 'ha-result-key-b';
const RESULT_KEY_C_ID = 'ha-result-key-c';
const INVOCATION_KEY_BYTE = 21;
const RESULT_KEY_A_BYTE = 22;
const RESULT_KEY_B_BYTE = 23;
const RESULT_KEY_C_BYTE = 24;
const EXPECTED_OUTPUT = Object.freeze({
summary: 'HA durable Tool result survived rekey and promotion',
});
function copyKey(byte) {
return Buffer.alloc(32, byte);
}
function snapshot() {
const generation = createPluginPackageResourceGenerationFromReferences({
installationId: 'install-ha-tool-result',
projectId: PROJECT_ID,
packageName: 'ha',
lockDigest: '8'.repeat(64),
generation: 1,
previousActiveLockDigest: null,
contentDigest: '9'.repeat(64),
resources: [],
});
return createProjectToolDefinitionSnapshot({
projectId: PROJECT_ID,
contributions: [
{
generation,
revisionDigest: 'a'.repeat(64),
definitions: [
{
...TOOL,
description: 'Read one bounded HA result fixture',
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,
},
],
},
],
});
}
function principal(baseTimeMs) {
return Object.freeze({
subject: SUBJECT,
authenticationId: 'auth-ha-tool-result',
authenticatedAtMs: baseTimeMs - 1_000,
expiresAtMs: baseTimeMs + 120_000,
assurance: 'multi_factor',
});
}
function authorizer() {
return Object.freeze({
async authorize() {
return Object.freeze({
effect: 'allow',
reasons: Object.freeze(['role_grant']),
fence: POLICY_FENCE,
});
},
});
}
function bindingRegistry(definitionSnapshot) {
const binding = createTrustedToolHandlerBinding(definitionSnapshot, {
tool: TOOL,
adapter: {
id: 'builtin.ha-result-read',
version: '1.0.0',
},
executionClass: 'builtin_in_process',
profiles: ['cluster-control'],
authorities: ['database.read'],
timeoutSeconds: 20,
redactionContract: {
id: 'redaction.ha-result-read',
version: '1.0.0',
},
auditContract: {
id: 'audit.tool-call',
version: '1.0.0',
},
});
return Object.freeze({
binding,
bindings: new TrustedToolHandlerBindingRegistry(definitionSnapshot, [
binding,
]),
});
}
async function startBundle(readyStepRun, baseTimeMs) {
const definitionSnapshot = snapshot();
const { binding, bindings } = bindingRegistry(definitionSnapshot);
assert.equal(binding.definitionDigest, readyStepRun.definitionDigest);
const invocation = await prepareToolInvocation(
projectToolDefinitionRegistry(definitionSnapshot),
{
projectId: PROJECT_ID,
principal: principal(baseTimeMs),
nowMs: baseTimeMs,
tool: TOOL,
input: { runId: RUN_ID },
},
authorizer(),
);
const planBundle = createTrustedToolInvocationPlan(bindings, invocation, {
actionRef: `tool-plan:${RUN_ID}`,
inputArtifactId: 'ha-tool-result-input-artifact',
previewArtifactId: 'ha-tool-result-preview-artifact',
artifactKeyId: INVOCATION_KEY_ID,
artifactKey: copyKey(INVOCATION_KEY_BYTE),
artifactNonce: Buffer.alloc(12, 24),
profile: 'cluster-control',
preview: {
title: 'HA Tool Result',
summary: 'Creates one encrypted completion before promotion',
fields: [
{
kind: 'identifier',
label: 'Run',
value: RUN_ID,
},
],
warnings: [],
},
sealedAtMs: baseTimeMs + 100,
});
const plan = planBundle.plan;
const startedAtMs = baseTimeMs + 200;
const evidence = createToolExecutionEvidenceBundle({
traceId: '5'.repeat(32),
spanId: '6'.repeat(16),
projectId: PROJECT_ID,
runId: RUN_ID,
stepRunId: readyStepRun.id,
invocationPlanDigest: plan.planDigest,
bindingDigest: binding.bindingDigest,
adapterDigest: trustedToolContractIdentityDigest(binding.adapter),
redactionContractDigest: trustedToolContractIdentityDigest(
binding.redactionContract,
),
auditContractDigest: trustedToolContractIdentityDigest(
binding.auditContract,
),
audit: {
eventId: '34000000-0000-4000-8000-000000000004',
requestId: 'ha-tool-result-start-request',
operationId: TOOL_EXECUTION_START_AUDIT_OPERATION,
projectId: PROJECT_ID,
subject: SUBJECT,
authenticationId: 'auth-ha-tool-result',
outcome: 'allowed',
reasons: ['tool_execution_start'],
fence: POLICY_FENCE,
occurredAtMs: startedAtMs,
},
createdAtMs: startedAtMs,
});
const admission = await admitTrustedToolExecution(bindings, plan, {
principal: principal(baseTimeMs),
profile: 'cluster-control',
nowMs: startedAtMs,
authorizer: authorizer(),
evidence: {
stepRun: {
id: readyStepRun.id,
version: readyStepRun.version,
digest: readyStepRun.stepRunDigest,
},
...toolExecutionAdmissionEvidence(evidence),
},
});
const mutation = transitionStepRunMutation(
readyStepRun,
{
expectedVersion: readyStepRun.version,
expectedDigest: readyStepRun.stepRunDigest,
mutationId: 'ha-tool-result-running-mutation',
to: 'running',
atMs: startedAtMs,
},
{
expectedRunVersion: 1,
expectedRunEventSequence: 1,
eventId: '34000000-0000-4000-8000-000000000003',
dedupeKey: 'ha-tool-result:running',
actor: SUBJECT,
},
);
return Object.freeze({
command: createToolExecutionStartCommand({
startId: START_ID,
admission,
evidence,
stepRunMutation: mutation,
}),
inputArtifact: planBundle.inputArtifact,
previewArtifact: planBundle.previewArtifact,
definitionSnapshot,
binding,
bindings,
startedAtMs,
});
}
function adapterRegistry(bindings, binding, executionCounter) {
return new TrustedToolExecutionAdapterRegistry(bindings, [
{
binding,
profile: 'cluster-control',
recoveryMode: 'retry_safe_read',
async execute() {
executionCounter.count += 1;
return EXPECTED_OUTPUT;
},
},
]);
}
function keyProvider(keyId, byte) {
return Object.freeze({
async resolve(requestedKeyId) {
return requestedKeyId === keyId
? Object.freeze({
keyId,
key: copyKey(byte),
})
: null;
},
});
}
function blockingKeyProvider(keyId, byte) {
let requestedResolve;
let releaseResolve;
let requested = false;
const keyRequested = new Promise((resolve) => {
requestedResolve = resolve;
});
const released = new Promise((resolve) => {
releaseResolve = resolve;
});
return Object.freeze({
keyRequested,
release() {
releaseResolve();
},
provider: Object.freeze({
async resolve(requestedKeyId) {
if (requestedKeyId !== keyId) return null;
if (!requested) {
requested = true;
requestedResolve();
}
await released;
return Object.freeze({
keyId,
key: copyKey(byte),
});
},
}),
});
}
function completionDependencies(options) {
const {
runtimePool,
definitionSnapshot,
binding,
bindings,
executionCounter,
resultKeyId,
resultKeyByte,
completedAtMs,
completionRepository,
resultKeyProvider,
} = options;
return Object.freeze({
barriers: new PostgresToolExecutionStartBarrierRepository(runtimePool),
artifacts: new PostgresToolInvocationArtifactRepository(runtimePool),
keys: keyProvider(INVOCATION_KEY_ID, INVOCATION_KEY_BYTE),
adapters: adapterRegistry(bindings, binding, executionCounter),
completions:
completionRepository ??
new PostgresToolExecutionCompletionRepository(runtimePool),
stepRuns: new PostgresStepRunRepository(runtimePool),
runs: new PostgresRunRepository(runtimePool),
resultKeyCatalog: new PostgresToolResultKeyCatalogReader(runtimePool),
resultRekeys: new PostgresToolResultRekeyReader(runtimePool),
resultKeys: resultKeyProvider ?? keyProvider(resultKeyId, resultKeyByte),
identities: Object.freeze({
create() {
return Object.freeze({
artifactId: 'ha-tool-result-output-artifact',
mutationId: 'ha-tool-result-succeeded-mutation',
eventId: '34000000-0000-4000-8000-000000000005',
});
},
}),
nonceFactory: () => Buffer.alloc(12, 25),
now: () => completedAtMs,
definitionSnapshot,
});
}
async function readBinding(pool, startId) {
const result = await pool.query(
`SELECT
start_id AS "startId",
artifact_id AS "artifactId",
artifact_digest AS "artifactDigest",
catalog_generation AS "catalogGeneration",
catalog_digest AS "catalogDigest",
key_id AS "keyId",
material_proof AS "materialProof",
binding_digest AS "bindingDigest"
FROM "ql3"."tool_execution_result_key_bindings"
WHERE start_id = $1`,
[startId],
);
assert.equal(result.rowCount, 1);
return normalizeToolExecutionResultKeyBinding({
schema: TOOL_EXECUTION_RESULT_KEY_BINDING_SCHEMA,
...result.rows[0],
});
}
async function completeParentRun(runtimePool, completion, completedAtMs) {
const runs = new PostgresRunRepository(runtimePool);
await runs.transaction(async (transaction) => {
const current = await transaction.findRunById(RUN_ID);
assert.ok(current);
assert.equal(current.status, 'running');
const next = Object.freeze({
...current,
status: 'succeeded',
version: current.version + 1,
eventSequence: current.eventSequence + 1,
outputRef: completion.resultArtifact.artifactId,
finishedAtMs: completedAtMs + 1,
});
assert.equal(
await transaction.compareAndSetRun(next, current.version),
true,
);
await transaction.appendEvent(
Object.freeze({
id: '34000000-0000-4000-8000-000000000006',
runId: RUN_ID,
sequence: next.eventSequence,
type: 'run.succeeded',
dedupeKey: 'ha-tool-result:run-succeeded',
actorType: 'system',
actorId: 'trusted-tool-runtime',
stepRunId: STEP_RUN_ID,
payload: Object.freeze({
resultArtifactId: completion.resultArtifact.artifactId,
}),
createdAtMs: completedAtMs + 1,
}),
);
});
}
async function reopen(runtimePool, fixture, expectedExecutionCount) {
const executionCounter = { count: 0 };
const result = await executeAndCompleteTrustedToolSuccess(
START_ID,
completionDependencies({
runtimePool,
definitionSnapshot: fixture.definitionSnapshot,
binding: fixture.binding,
bindings: fixture.bindings,
executionCounter,
resultKeyId: fixture.resultKeyId,
resultKeyByte: fixture.resultKeyByte,
completedAtMs: fixture.completedAtMs,
}),
);
assert.equal(result.status, 'existing');
assert.deepEqual(result.output, EXPECTED_OUTPUT);
assert.equal(executionCounter.count, expectedExecutionCount);
return result;
}
async function persistNonEmptyToolResultRetirement(options) {
const {
runtimePool,
adminPool,
catalogCommitFaultPool,
catalogCommitFaultEvidence,
completionCommitFaultPool,
completionCommitFaultEvidence,
rekeyCommitFaultPool,
rekeyCommitFaultEvidence,
baseTimeMs,
} = options;
const catalogs = new PostgresToolResultKeyCatalogRepository(adminPool);
const initialCatalog = await catalogs.append(
createToolResultKeyCatalogBootstrapCommand({
keyId: RESULT_KEY_A_ID,
materialProof: toolResultKeyMaterialProof(
RESULT_KEY_A_ID,
copyKey(RESULT_KEY_A_BYTE),
),
mutationId: 'ha-tool-result-key-bootstrap-a',
}),
);
await runtimePool.query(
`INSERT INTO "ql3"."projects" (
id, name, slug, status, version, created_at_ms, updated_at_ms
) VALUES (
$1, 'HA Tool Result', 'ha-tool-result', 'active', 1, $2, $2
)`,
[PROJECT_ID, baseTimeMs],
);
await runtimePool.query(
`INSERT INTO "ql3"."runs" (
id, project_id, task_id, task_revision, trigger_type,
execution_origin, execution_owner, status, version,
event_sequence, priority, created_at_ms
) VALUES (
$1, $2, 'ha-tool-result-task', 'v1', 'manual',
'manual', 'runtime', 'running', 0, 0, 0, $3
)`,
[RUN_ID, PROJECT_ID, baseTimeMs],
);
const definitionSnapshot = snapshot();
const creation = createStepRunMutation(
{
id: STEP_RUN_ID,
runId: RUN_ID,
stepKey: 'workflow.ha-result-read',
kind: 'tool',
definitionRef: `tool:${TOOL.name}@${TOOL.version}`,
definitionDigest: definitionSnapshot.definitions[0].definitionDigest,
required: true,
initialStatus: 'ready',
inputRef: 'artifact:ha-tool-result:input',
mutationId: 'ha-tool-result-create-mutation',
createdAtMs: baseTimeMs + 50,
},
{
expectedRunVersion: 0,
expectedRunEventSequence: 0,
eventId: '34000000-0000-4000-8000-000000000002',
dedupeKey: 'ha-tool-result:create',
actor: SUBJECT,
},
);
const steps = new PostgresStepRunRepository(runtimePool);
assert.equal((await steps.apply(creation)).status, 'applied');
const bundle = await startBundle(creation.stepRun, baseTimeMs);
const artifacts = new PostgresToolInvocationArtifactRepository(runtimePool);
assert.deepEqual(
await artifacts.put(bundle.inputArtifact, bundle.previewArtifact),
{ status: 'inserted' },
);
const starts = new PostgresToolExecutionStartBarrierRepository(runtimePool);
const start = await starts.prepare(bundle.command);
assert.equal(start.status, 'created');
const completedAtMs = bundle.startedAtMs + 100;
const staleExecutionCounter = { count: 0 };
const blockedResultKey = blockingKeyProvider(
RESULT_KEY_A_ID,
RESULT_KEY_A_BYTE,
);
const staleCompletion = executeAndCompleteTrustedToolSuccess(
START_ID,
completionDependencies({
runtimePool,
definitionSnapshot: bundle.definitionSnapshot,
binding: bundle.binding,
bindings: bundle.bindings,
executionCounter: staleExecutionCounter,
resultKeyId: RESULT_KEY_A_ID,
resultKeyByte: RESULT_KEY_A_BYTE,
resultKeyProvider: blockedResultKey.provider,
completedAtMs,
}),
);
await blockedResultKey.keyRequested;
assert.equal(staleExecutionCounter.count, 1);
const rotateToBCommand = createToolResultKeyRotationCommand(
initialCatalog.catalog,
{
keyId: RESULT_KEY_B_ID,
materialProof: toolResultKeyMaterialProof(
RESULT_KEY_B_ID,
copyKey(RESULT_KEY_B_BYTE),
),
mutationId: 'ha-tool-result-key-rotate-b',
},
);
try {
await assert.rejects(
new PostgresToolResultKeyCatalogRepository(catalogCommitFaultPool).append(
rotateToBCommand,
),
(error) => error?.code === 'TOOL_RESULT_KEY_CATALOG_UNAVAILABLE',
);
} finally {
blockedResultKey.release();
}
assert.deepEqual(catalogCommitFaultEvidence, {
injected: true,
commitCompletedBeforeFault: true,
backendTerminationRequested: true,
backendConnectionRejected: true,
});
const rotated = await catalogs.append(rotateToBCommand);
assert.equal(rotated.status, 'existing');
await assert.rejects(
staleCompletion,
(error) => error?.code === 'TOOL_EXECUTION_COMPLETION_CONFLICT',
);
const staleFacts = await runtimePool.query(
`SELECT
step.status AS "stepStatus",
step.version AS "stepVersion",
run.version AS "runVersion",
run.event_sequence AS "runEventSequence",
(SELECT count(*)::integer
FROM "ql3"."tool_execution_completions"
WHERE start_id = $3) AS "completionCount",
(SELECT count(*)::integer
FROM "ql3"."tool_execution_result_key_bindings"
WHERE start_id = $3) AS "bindingCount"
FROM "ql3"."step_runs" AS step
JOIN "ql3"."runs" AS run ON run.id = step.run_id
WHERE step.id = $1 AND run.id = $2`,
[STEP_RUN_ID, RUN_ID, START_ID],
);
assert.equal(staleFacts.rowCount, 1);
assert.deepEqual(staleFacts.rows[0], {
stepStatus: 'running',
stepVersion: 2,
runVersion: 2,
runEventSequence: 2,
completionCount: 0,
bindingCount: 0,
});
const winningExecutionCounter = { count: 0 };
const completion = await executeAndCompleteTrustedToolSuccess(
START_ID,
completionDependencies({
runtimePool,
definitionSnapshot: bundle.definitionSnapshot,
binding: bundle.binding,
bindings: bundle.bindings,
executionCounter: winningExecutionCounter,
resultKeyId: RESULT_KEY_B_ID,
resultKeyByte: RESULT_KEY_B_BYTE,
completionRepository: new PostgresToolExecutionCompletionRepository(
completionCommitFaultPool,
),
completedAtMs,
}),
);
assert.equal(completion.status, 'existing');
assert.equal(winningExecutionCounter.count, 1);
assert.deepEqual(completion.output, EXPECTED_OUTPUT);
assert.deepEqual(completionCommitFaultEvidence, {
injected: true,
commitCompletedBeforeFault: true,
backendTerminationRequested: true,
backendConnectionRejected: true,
});
await completeParentRun(runtimePool, completion.completion, completedAtMs);
const completionRepository = new PostgresToolExecutionCompletionRepository(
runtimePool,
);
const artifact = await completionRepository.findResultArtifact(
completion.completion.resultArtifact.artifactId,
);
assert.ok(artifact);
const sourceBinding = await readBinding(adminPool, START_ID);
const rotatedToC = await catalogs.append(
createToolResultKeyRotationCommand(rotated.catalog, {
keyId: RESULT_KEY_C_ID,
materialProof: toolResultKeyMaterialProof(
RESULT_KEY_C_ID,
copyKey(RESULT_KEY_C_BYTE),
),
mutationId: 'ha-tool-result-key-rotate-c',
}),
);
const rekeyCommand = createToolExecutionResultRekeyCommand({
artifact,
binding: sourceBinding,
previousOverlay: null,
overlayId: 'ha-tool-result-rekey-overlay',
mutationId: 'ha-tool-result-rekey-mutation',
targetCatalogFence: toolResultKeyCatalogFence(
rotatedToC.catalog,
requireActiveToolResultKey(rotatedToC.catalog),
),
targetKey: copyKey(RESULT_KEY_C_BYTE),
output: completion.output,
rekeyedAtMs: completedAtMs + 1,
registry: bundle.bindings.definitionRegistry(),
nonceFactory: () => Buffer.alloc(12, 26),
});
await assert.rejects(
new PostgresToolResultRekeyRepository(rekeyCommitFaultPool).append(
rekeyCommand,
),
(error) => error?.code === 'TOOL_EXECUTION_RESULT_REKEY_UNAVAILABLE',
);
assert.deepEqual(rekeyCommitFaultEvidence, {
injected: true,
commitCompletedBeforeFault: true,
backendTerminationRequested: true,
backendConnectionRejected: true,
});
const rekeys = new PostgresToolResultRekeyRepository(adminPool);
const rekeyReplay = await rekeys.append(rekeyCommand);
assert.equal(rekeyReplay.status, 'existing');
const receipt = await rekeys.create(
createToolResultKeyRetirementReceiptCommand({
expectedCatalogGeneration: rotatedToC.catalog.generation,
expectedCatalogDigest: rotatedToC.catalog.catalogDigest,
keyId: RESULT_KEY_B_ID,
mutationId: 'ha-tool-result-retirement-receipt',
}),
);
assert.equal(receipt.status, 'created');
assert.equal(receipt.receipt.bindingCount, 1);
assert.equal(receipt.receipt.overlayHeadCount, 1);
const retired = await catalogs.append(
createToolResultKeyRetirementCommand(rotatedToC.catalog, {
keyId: RESULT_KEY_B_ID,
retirementReceiptDigest: receipt.receipt.receiptDigest,
mutationId: 'ha-tool-result-key-retire-b',
}),
);
assert.equal(
retired.catalog.keys.find((entry) => entry.keyId === RESULT_KEY_B_ID).state,
'retired',
);
const fixture = Object.freeze({
definitionSnapshot: bundle.definitionSnapshot,
binding: bundle.binding,
bindings: bundle.bindings,
completedAtMs,
resultKeyId: RESULT_KEY_C_ID,
resultKeyByte: RESULT_KEY_C_BYTE,
});
await reopen(runtimePool, fixture, 0);
return {
fixture,
report: {
startId: START_ID,
artifactId: artifact.artifactId,
activeKeyId: RESULT_KEY_C_ID,
retiredKeyId: RESULT_KEY_B_ID,
catalogGeneration: retired.catalog.generation,
catalogDigest: retired.catalog.catalogDigest,
retirementReceiptDigest: receipt.receipt.receiptDigest,
bindingCount: receipt.receipt.bindingCount,
overlayHeadCount: receipt.receipt.overlayHeadCount,
overlayRevision: rekeyReplay.overlay.revision,
catalogRotationCompletionRace: {
catalogReadBlockedBeforeCompletionCommit: true,
...catalogCommitFaultEvidence,
replayStatus: rotated.status,
staleCompletionRejected: true,
partialCompletionCount: staleFacts.rows[0].completionCount,
partialBindingCount: staleFacts.rows[0].bindingCount,
stepStatusAfterRejection: staleFacts.rows[0].stepStatus,
runVersionAfterRejection: staleFacts.rows[0].runVersion,
runEventSequenceAfterRejection: staleFacts.rows[0].runEventSequence,
},
completionCommitResponseLoss: {
...completionCommitFaultEvidence,
coordinatorRecoveryStatus: completion.status,
adapterExecutionsInWinningAttempt: winningExecutionCounter.count,
adapterExecutionsAcrossRaceAndRetry:
staleExecutionCounter.count + winningExecutionCounter.count,
},
rekeyCommitResponseLoss: {
...rekeyCommitFaultEvidence,
replayStatus: rekeyReplay.status,
},
primaryUnifiedReopen: true,
replicatedBeforePromotion: false,
promotedUnifiedReopen: false,
survivedPromotion: false,
},
};
}
async function verifyPromotedNonEmptyToolResult(options) {
const { runtimePool, adminPool, fixture, report } = options;
const catalog = await new PostgresToolResultKeyCatalogRepository(
adminPool,
).findCurrent();
const receipt = await new PostgresToolResultRekeyRepository(
adminPool,
).findByDigest(report.retirementReceiptDigest);
assert.ok(catalog);
assert.ok(receipt);
assert.equal(catalog.generation, report.catalogGeneration);
assert.equal(catalog.catalogDigest, report.catalogDigest);
assert.equal(catalog.activeKeyId, report.activeKeyId);
assert.equal(
catalog.keys.find((entry) => entry.keyId === report.retiredKeyId).state,
'retired',
);
assert.equal(receipt.bindingCount, 1);
assert.equal(receipt.overlayHeadCount, 1);
const reopened = await reopen(runtimePool, fixture, 0);
assert.equal(reopened.completion.startId, report.startId);
report.promotedUnifiedReopen = true;
report.survivedPromotion = true;
}
module.exports = {
persistNonEmptyToolResultRetirement,
verifyPromotedNonEmptyToolResult,
};