feat(ql3): isolate plugin secret action execution

This commit is contained in:
whyour
2026-08-14 01:49:54 +08:00
parent 53c150c4ec
commit 75dbe7a4c4
16 changed files with 1044 additions and 58 deletions
@@ -74,6 +74,10 @@ export interface ApprovedActionDispatchBatchSummary {
readonly nextCursor?: Readonly<ApprovedActionExecutionCursor>;
}
export interface ApprovedActionDispatchByIdOptions {
readonly dispatchId: string;
}
interface MutableSummary {
scanned: number;
claimed: number;
@@ -262,6 +266,54 @@ export class ApprovedActionDispatcher {
return Object.freeze({ ...summary });
}
/**
* Execute one durable dispatch without scanning the shared due queue.
*
* This is the entry point for an action-scoped executor (for example a
* Kubernetes Job with an exact Secret projection). The handler check happens
* before the claim so a narrowly configured executor cannot lease and block
* an action outside its authority.
*/
async dispatchById(
options: Readonly<ApprovedActionDispatchByIdOptions>,
): Promise<Readonly<ApprovedActionDispatchBatchSummary>> {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
!exactKeys(options, ['dispatchId'])
) {
throw new TypeError('Approved Action exact dispatch is invalid');
}
const dispatchId = identifier(options.dispatchId, 'dispatch id');
const summary: MutableSummary = {
scanned: 0,
claimed: 0,
started: 0,
succeeded: 0,
failed: 0,
blocked: 0,
retrying: 0,
deferred: 0,
recoveryRequired: 0,
alreadyTerminal: 0,
unavailable: 0,
truncated: false,
};
const snapshot = await this.#find(dispatchId);
if (!snapshot) {
summary.unavailable = 1;
return Object.freeze({ ...summary });
}
summary.scanned = 1;
if (!this.#handlers.has(snapshot.dispatch.action.actionType)) {
summary.unavailable = 1;
return Object.freeze({ ...summary });
}
await this.#dispatchOne(dispatchId, summary);
return Object.freeze({ ...summary });
}
async #dispatchOne(
dispatchId: string,
summary: MutableSummary,
@@ -302,3 +302,43 @@ test('does not claim an action without a matching handler', async () => {
assert.equal(repository.execution.status, 'pending');
assert.equal(repository.startCalls, 0);
});
test('dispatches only the requested durable action without a queue scan', async () => {
const repository = new InMemoryExecutionRepository(dispatch());
repository.listDueExecutions = async () => {
throw new Error('exact dispatch must not scan');
};
const summary = await createDispatcher(repository, {
actionType: 'plugin_package.install',
async inspect(value) {
return { status: 'ready', actionDigest: value.action.actionDigest };
},
async execute() {
return {
outcome: 'succeeded',
resultCode: 'package_admitted',
resultDigest: RESULT_DIGEST,
};
},
}).dispatchById({ dispatchId: 'dispatch-dispatcher-v1' });
assert.equal(summary.scanned, 1);
assert.equal(summary.claimed, 1);
assert.equal(summary.succeeded, 1);
assert.equal(summary.truncated, false);
});
test('exact dispatch does not claim an action outside configured authority', async () => {
const repository = new InMemoryExecutionRepository(dispatch());
const dispatcher = new ApprovedActionDispatcher(repository, [], {
owner: 'dispatcher_instance_1',
clock: () => 100,
createId: () => 'dispatcher-exact-id',
});
const summary = await dispatcher.dispatchById({
dispatchId: 'dispatch-dispatcher-v1',
});
assert.equal(summary.scanned, 1);
assert.equal(summary.claimed, 0);
assert.equal(summary.unavailable, 1);
assert.equal(repository.execution.status, 'pending');
});