feat(ql3): add request-scoped console task creation

This commit is contained in:
whyour
2026-08-29 03:51:35 +08:00
parent 53b7072370
commit 884912d1c8
32 changed files with 2485 additions and 56 deletions
@@ -22,6 +22,7 @@ function request(overrides = {}) {
runId: 'run_123',
}),
authorization: 'Bearer opaque',
localPresence: null,
signal: new AbortController().signal,
...overrides,
});
@@ -35,6 +36,17 @@ function fixture(overrides = {}) {
events.push('authenticate');
return Object.freeze({
principal: PRINCIPAL,
credentialFence: Object.freeze({
credentialId: 'credential-local',
credentialVersion: 1,
pepperKeyId: 'owner-v1',
materialDigest: 'a'.repeat(64),
subjectType: 'user',
subjectId: 'usr_local',
secretDigest: 'b'.repeat(64),
notBeforeAtMs: 1,
expiresAtMs: 20_000,
}),
async confirm() {
events.push('confirm');
},
@@ -127,6 +139,12 @@ function fixture(overrides = {}) {
return { statusCode: 202, body: { status: 'accepted' } };
},
},
taskPutRoute: {
async handle(value) {
events.push(`task-put:${value.projectId}:${value.taskId}`);
return { statusCode: 201, body: { status: 'created' } };
},
},
now: () => 10_000,
randomUuid: () => '019f70c0-0000-4000-8000-000000000002',
...overrides,
@@ -382,6 +400,28 @@ test('authorizes and audits run.start before exposing the Task body handler', as
assert.equal(events.at(-1), 'task-start:prj_default:task-a');
});
test('defers Task put Policy, audit and strong confirmation to the request-bound route', async () => {
const { admission, events } = fixture();
const prepared = await admission.prepare(
request({
operation: Object.freeze({
operationId: 'task.put',
projectId: 'prj_default',
taskId: 'task-a',
}),
localPresence: 'ql3p_proof',
}),
);
assert.equal(prepared.bodyMode, 'json');
assert.equal(prepared.maximumBodyBytes, 72 * 1024);
assert.deepEqual(events, ['authenticate']);
assert.deepEqual(await prepared.handle({ name: 'Task' }), {
statusCode: 201,
body: { status: 'created' },
});
assert.deepEqual(events, ['authenticate', 'task-put:prj_default:task-a']);
});
test('audits authentication rejection before returning a challenge', async () => {
const events = [];
const { admission } = fixture({