feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
@@ -0,0 +1,405 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createLocalApiAdmission,
} = require('../dist/admission/localApiAdmission.js');
const PRINCIPAL = Object.freeze({
subject: Object.freeze({ type: 'user', id: 'usr_local' }),
authenticationId: 'credential:local',
authenticatedAtMs: 9_000,
expiresAtMs: 11_000,
assurance: 'single_factor',
});
function request(overrides = {}) {
return Object.freeze({
requestId: 'local:019f70c0-0000-7000-8000-000000000001',
operation: Object.freeze({
operationId: 'run.get',
projectId: 'prj_default',
runId: 'run_123',
}),
authorization: 'Bearer opaque',
signal: new AbortController().signal,
...overrides,
});
}
function fixture(overrides = {}) {
const events = [];
const options = {
authenticator: {
async authenticate() {
events.push('authenticate');
return Object.freeze({
principal: PRINCIPAL,
async confirm() {
events.push('confirm');
},
});
},
},
policy: {
async authorize(principal, projectId, permission) {
assert.equal(principal, PRINCIPAL);
events.push(`authorize:${permission}:${projectId}`);
return {
effect: 'allow',
reasons: ['role_grant'],
fence: { projectVersion: 2, bindingVersion: 3 },
};
},
},
audit: {
async record(record) {
events.push(`audit:${record.outcome}:${record.operationId}`);
},
},
runReadRoute: {
async handle(value) {
events.push(`route:${value.projectId}:${value.runId}`);
return { statusCode: 200, body: { run: { id: value.runId } } };
},
},
runListRoute: {
async handle(value) {
events.push(`list:${value.projectId}:${value.input.limit ?? 32}`);
return { statusCode: 200, body: { runs: [], hasMore: false } };
},
},
runEventListRoute: {
async handle(value) {
events.push(
`events:${value.projectId}:${value.runId}:${
value.input.afterSequence ?? 0
}`,
);
return {
statusCode: 200,
body: { events: [], hasMore: false, nextAfterSequence: 0 },
};
},
},
runStepListRoute: {
async handle(value) {
events.push(
`steps:${value.projectId}:${value.runId}:${
value.input.after?.stepKey ?? 'start'
}`,
);
return {
statusCode: 200,
body: { steps: [], hasMore: false, next: null },
};
},
},
runCancellationRoute: {
async handle(value) {
events.push(`cancel:${value.projectId}:${value.runId}`);
return { statusCode: 202, body: { status: 'accepted' } };
},
},
taskListRoute: {
async handle(value) {
events.push(`tasks:${value.projectId}:${value.input.limit ?? 32}`);
return { statusCode: 200, body: { tasks: [], hasMore: false } };
},
},
taskReadRoute: {
async handle(value) {
events.push(`task:${value.projectId}:${value.taskId}`);
return { statusCode: 200, body: { task: { taskId: value.taskId } } };
},
},
taskStartRoute: {
async handle(value) {
events.push(`task-start:${value.projectId}:${value.taskId}`);
return { statusCode: 202, body: { status: 'accepted' } };
},
},
now: () => 10_000,
randomUuid: () => '019f70c0-0000-4000-8000-000000000002',
...overrides,
};
return { admission: createLocalApiAdmission(options), events };
}
async function execute(admission, value, body = null) {
const prepared = await admission.prepare(value);
return typeof prepared.handle === 'function'
? prepared.handle(body)
: prepared;
}
test('authenticates, authorizes, durably audits and re-confirms before reading', async () => {
const { admission, events } = fixture();
assert.deepEqual(await execute(admission, request()), {
statusCode: 200,
body: { run: { id: 'run_123' } },
});
assert.deepEqual(events, [
'authenticate',
'authorize:run.read:prj_default',
'audit:allowed:run.get',
'confirm',
'route:prj_default:run_123',
]);
});
test('uses the same admission chain with a route-owned run.list audit identity', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: Object.freeze({
operationId: 'run.list',
projectId: 'prj_default',
input: Object.freeze({ limit: 8 }),
}),
}),
),
{ statusCode: 200, body: { runs: [], hasMore: false } },
);
assert.deepEqual(events, [
'authenticate',
'authorize:run.read:prj_default',
'audit:allowed:run.list',
'confirm',
'list:prj_default:8',
]);
});
test('uses the same admission chain with a route-owned run.events.list audit identity', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: Object.freeze({
operationId: 'run.events.list',
projectId: 'prj_default',
runId: 'run_123',
input: Object.freeze({ afterSequence: 7, limit: 8 }),
}),
}),
),
{
statusCode: 200,
body: { events: [], hasMore: false, nextAfterSequence: 0 },
},
);
assert.deepEqual(events, [
'authenticate',
'authorize:run.read:prj_default',
'audit:allowed:run.events.list',
'confirm',
'events:prj_default:run_123:7',
]);
});
test('uses the same admission chain with a route-owned run.steps.list audit identity', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: Object.freeze({
operationId: 'run.steps.list',
projectId: 'prj_default',
runId: 'run_123',
input: Object.freeze({
after: Object.freeze({
stepKey: 'build',
stepRunId: 'step_1',
}),
limit: 8,
}),
}),
}),
),
{ statusCode: 200, body: { steps: [], hasMore: false, next: null } },
);
assert.deepEqual(events, [
'authenticate',
'authorize:run.read:prj_default',
'audit:allowed:run.steps.list',
'confirm',
'steps:prj_default:run_123:build',
]);
});
test('uses task.read with a route-owned task.list audit identity', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: Object.freeze({
operationId: 'task.list',
projectId: 'prj_default',
input: Object.freeze({ limit: 8 }),
}),
}),
),
{ statusCode: 200, body: { tasks: [], hasMore: false } },
);
assert.deepEqual(events, [
'authenticate',
'authorize:task.read:prj_default',
'audit:allowed:task.list',
'confirm',
'tasks:prj_default:8',
]);
});
test('uses task.read with a route-owned task.get audit identity', async () => {
const { admission, events } = fixture();
assert.deepEqual(
await execute(
admission,
request({
operation: Object.freeze({
operationId: 'task.get',
projectId: 'prj_default',
taskId: 'task-a',
}),
}),
),
{ statusCode: 200, body: { task: { taskId: 'task-a' } } },
);
assert.deepEqual(events, [
'authenticate',
'authorize:task.read:prj_default',
'audit:allowed:task.get',
'confirm',
'task:prj_default:task-a',
]);
});
test('authorizes and audits run.stop before exposing the cancellation body handler', async () => {
const { admission, events } = fixture();
const prepared = await admission.prepare(
request({
operation: Object.freeze({
operationId: 'run.cancel',
projectId: 'prj_default',
runId: 'run_123',
}),
}),
);
assert.equal(typeof prepared.handle, 'function');
assert.equal(prepared.bodyMode, 'json');
assert.equal(prepared.maximumBodyBytes, 512);
assert.deepEqual(events, [
'authenticate',
'authorize:run.stop:prj_default',
'audit:allowed:run.cancel',
'confirm',
]);
assert.deepEqual(await prepared.handle({ schema: 'x' }), {
statusCode: 202,
body: { status: 'accepted' },
});
assert.equal(events.at(-1), 'cancel:prj_default:run_123');
});
test('authorizes and audits run.start before exposing the Task body handler', async () => {
const { admission, events } = fixture();
const prepared = await admission.prepare(
request({
operation: Object.freeze({
operationId: 'task.start',
projectId: 'prj_default',
taskId: 'task-a',
}),
}),
);
assert.equal(prepared.bodyMode, 'json');
assert.equal(prepared.maximumBodyBytes, 512);
assert.deepEqual(events, [
'authenticate',
'authorize:run.start:prj_default',
'audit:allowed:task.start',
'confirm',
]);
assert.equal((await prepared.handle({ schema: 'x' })).statusCode, 202);
assert.equal(events.at(-1), 'task-start:prj_default:task-a');
});
test('audits authentication rejection before returning a challenge', async () => {
const events = [];
const { admission } = fixture({
authenticator: {
async authenticate() {
events.push('authenticate');
return null;
},
},
audit: {
async record(record) {
events.push(`audit:${record.outcome}`);
},
},
});
assert.deepEqual(await execute(admission, request()), {
statusCode: 401,
body: { code: 'authentication_required' },
});
assert.deepEqual(events, ['authenticate', 'audit:authentication_rejected']);
});
test('does not confirm or route denied, unaudited or changed credentials', async () => {
const denied = fixture({
policy: {
async authorize() {
return { effect: 'deny', reasons: ['no_binding'], fence: null };
},
},
});
assert.deepEqual(await execute(denied.admission, request()), {
statusCode: 403,
body: { code: 'forbidden' },
});
assert.equal(denied.events.includes('confirm'), false);
assert.equal(
denied.events.some((event) => event.startsWith('route:')),
false,
);
const unaudited = fixture({
audit: {
async record() {
throw new Error('audit unavailable');
},
},
});
assert.deepEqual(await execute(unaudited.admission, request()), {
statusCode: 503,
body: { code: 'security_audit_unavailable' },
});
assert.equal(unaudited.events.includes('confirm'), false);
const changed = fixture({
authenticator: {
async authenticate() {
return {
principal: PRINCIPAL,
async confirm() {
throw new Error('credential rotated');
},
};
},
},
});
assert.deepEqual(await execute(changed.admission, request()), {
statusCode: 503,
body: { code: 'authentication_unavailable' },
});
assert.equal(
changed.events.some((event) => event.startsWith('route:')),
false,
);
});
+18
View File
@@ -0,0 +1,18 @@
const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const path = require('node:path');
const { test } = require('node:test');
test('publishes bounded help without bootstrapping storage or a listener', () => {
const result = spawnSync(
process.execPath,
[path.resolve(__dirname, '../dist/cli.js'), '--help'],
{ encoding: 'utf8' },
);
assert.equal(result.status, 0);
assert.equal(result.stderr, '');
assert.equal(
result.stdout,
'Usage: ql3-local-api --config /absolute/private-config.json\n',
);
});
@@ -0,0 +1,44 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
LOCAL_API_PROCESS_CONFIG_SCHEMA,
LocalApiProcessConfigError,
normalizeLocalApiProcessConfig,
} = require('../dist/production-process/config.js');
function candidate(overrides = {}) {
return {
schema: LOCAL_API_PROCESS_CONFIG_SCHEMA,
deploymentRoot: '/srv/qinglong',
applicationConfigFilePath: '/srv/qinglong/private/application.json',
ownerPepperKeyringDirectory: '/srv/qinglong/private/owner-pepper',
listener: { host: '127.0.0.1', port: 5701 },
...overrides,
};
}
test('normalizes one exact loopback-only Local API process configuration', () => {
assert.deepEqual(normalizeLocalApiProcessConfig(candidate()), candidate());
assert.deepEqual(
normalizeLocalApiProcessConfig(
candidate({ listener: { host: '::1', port: 65535 } }),
).listener,
{ host: '::1', port: 65535 },
);
});
test('rejects remote listeners, privileged ports and path authority escapes', () => {
for (const value of [
candidate({ listener: { host: '0.0.0.0', port: 5701 } }),
candidate({ listener: { host: '127.0.0.1', port: 80 } }),
candidate({ applicationConfigFilePath: '/srv/application.json' }),
candidate({ ownerPepperKeyringDirectory: '/srv/qinglong' }),
{ ...candidate(), unexpected: true },
]) {
assert.throws(
() => normalizeLocalApiProcessConfig(value),
LocalApiProcessConfigError,
);
}
});
@@ -0,0 +1,124 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const {
LocalOwnerPepperKeyringFileProvider,
provisionLocalOwnerPepperKey,
} = require('@qinglong/local-owner-console/pepper-custody');
const {
apiCredentialSecretDigest,
formatApiCredentialToken,
} = require('@qinglong/runtime-core/api-credential-token');
const {
LocalApiCredentialAuthenticationUnavailableError,
createLocalApiCredentialAuthenticator,
} = require('../dist/authentication/credentialAuthenticator.js');
const NOW = 1_800_000_000_000;
const CREDENTIAL_ID = 'local-api-owner';
const PEPPER_KEY_ID = 'owner-pepper-v1';
const SECRET = Buffer.alloc(32, 42).toString('base64url');
const PEPPER = Buffer.alloc(32, 43).toString('base64url');
const TOKEN = formatApiCredentialToken(CREDENTIAL_ID, SECRET);
function fixture(t) {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-api-auth-'));
fs.chmodSync(directory, 0o700);
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
const summary = provisionLocalOwnerPepperKey({
keyringDirectory: directory,
pepperKeyId: PEPPER_KEY_ID,
randomBytes: () => Buffer.alloc(32, 43),
});
const credential = {
credentialId: CREDENTIAL_ID,
version: 1,
pepperKeyId: PEPPER_KEY_ID,
state: 'active',
subject: { type: 'user', id: 'user-local-api' },
subjectStatus: 'active',
secretDigest: apiCredentialSecretDigest(
PEPPER,
CREDENTIAL_ID,
SECRET,
),
createdAtMs: NOW - 1_000,
notBeforeAtMs: NOW - 1_000,
expiresAtMs: NOW + 60_000,
};
const pepperKey = {
pepperKeyId: PEPPER_KEY_ID,
materialDigest: summary.digest,
backupDigest: 'b'.repeat(64),
state: 'active',
version: 2,
registeredAtMs: NOW - 2_000,
activatedAtMs: NOW - 1_500,
};
const authority = {
profile: 'edge',
runs: {},
apiCredentials: {
async resolve(credentialId) {
return credentialId === CREDENTIAL_ID ? { ...credential } : null;
},
},
ownerPepper: {
async resolveKey(pepperKeyId) {
return pepperKeyId === PEPPER_KEY_ID ? { ...pepperKey } : null;
},
},
projectPolicy: {},
securityAudit: {},
};
return {
authority,
credential,
pepperKey,
provider: new LocalOwnerPepperKeyringFileProvider(directory),
};
}
test('authenticates one exact Bearer credential and re-confirms its authority fence', async (t) => {
const value = fixture(t);
const authenticator = createLocalApiCredentialAuthenticator(
value.authority,
value.provider,
{ now: () => NOW },
);
assert.equal(await authenticator.authenticate(`Basic ${TOKEN}`), null);
assert.equal(await authenticator.authenticate('Bearer malformed'), null);
const authentication = await authenticator.authenticate(`Bearer ${TOKEN}`);
assert.deepEqual(authentication.principal.subject, {
type: 'user',
id: 'user-local-api',
});
await authentication.confirm();
});
test('fails closed when credential or pepper authority changes after audit', async (t) => {
const value = fixture(t);
const authenticator = createLocalApiCredentialAuthenticator(
value.authority,
value.provider,
{ now: () => NOW },
);
const credentialRevoked = await authenticator.authenticate(`Bearer ${TOKEN}`);
value.credential.state = 'revoked';
await assert.rejects(
credentialRevoked.confirm(),
LocalApiCredentialAuthenticationUnavailableError,
);
value.credential.state = 'active';
const pepperChanged = await authenticator.authenticate(`Bearer ${TOKEN}`);
value.pepperKey.materialDigest = 'f'.repeat(64);
await assert.rejects(
pepperChanged.confirm(),
LocalApiCredentialAuthenticationUnavailableError,
);
});
@@ -0,0 +1,653 @@
const assert = require('node:assert/strict');
const http = require('node:http');
const net = require('node:net');
const { test } = require('node:test');
const {
startLocalApiHttpSurface,
} = require('../dist/transport/httpSurface.js');
function reservePort() {
return new Promise((resolve, reject) => {
const server = net.createServer();
server.once('error', reject);
server.listen(0, '127.0.0.1', () => {
const address = server.address();
server.close((error) => {
if (error) reject(error);
else resolve(address.port);
});
});
});
}
function request(port, path, options = {}) {
return new Promise((resolve, reject) => {
const outgoing = http.request(
{
host: '127.0.0.1',
port,
path,
method: options.method ?? 'GET',
headers: options.headers ?? { authorization: 'Bearer opaque' },
},
(response) => {
const chunks = [];
response.on('data', (chunk) => chunks.push(chunk));
response.on('end', () =>
resolve({
statusCode: response.statusCode,
headers: response.headers,
body: JSON.parse(Buffer.concat(chunks).toString('utf8')),
}),
);
},
);
outgoing.once('error', reject);
if (options.body) outgoing.write(options.body);
outgoing.end();
});
}
function preparedAdmission(handler) {
return {
async prepare(value) {
const json = ['run.cancel', 'task.start'].includes(
value.operation.operationId,
);
return {
bodyMode: json ? 'json' : 'none',
maximumBodyBytes: json ? 512 : 0,
handle(body) {
return handler(value, body);
},
};
},
};
}
test('serves only the fixed canonical loopback Run route and drains idempotently', async (t) => {
const port = await reservePort();
const observed = [];
const surface = await startLocalApiHttpSurface({
profile: 'edge',
host: '127.0.0.1',
port,
admission: preparedAdmission(async (value, body) => {
observed.push(value);
if (
value.operation.operationId === 'run.cancel' ||
value.operation.operationId === 'task.start'
) {
return { statusCode: 202, body: { accepted: body } };
}
if (value.operation.operationId === 'run.get') {
return {
statusCode: 200,
body: { run: { id: value.operation.runId } },
};
}
if (value.operation.operationId === 'run.events.list') {
return {
statusCode: 200,
body: {
events: [],
hasMore: false,
nextAfterSequence: value.operation.input.afterSequence ?? 0,
},
};
}
if (value.operation.operationId === 'run.steps.list') {
return {
statusCode: 200,
body: {
steps: [],
hasMore: false,
next: value.operation.input.after ?? null,
},
};
}
if (value.operation.operationId === 'task.list') {
return {
statusCode: 200,
body: {
tasks: [],
hasMore: false,
input: value.operation.input,
},
};
}
if (value.operation.operationId === 'task.get') {
return {
statusCode: 200,
body: { task: { taskId: value.operation.taskId } },
};
}
return {
statusCode: 200,
body: { runs: [], hasMore: false, input: value.operation.input },
};
}),
randomUuid: () => '019f70c0-0000-4000-8000-000000000003',
});
t.after(() => surface.stopAndDrain());
const accepted = await request(
port,
'/api/v3/projects/prj_default/runs/run_123',
);
assert.equal(accepted.statusCode, 200);
assert.deepEqual(accepted.body, { run: { id: 'run_123' } });
assert.equal(accepted.headers['cache-control'], 'no-store');
assert.equal(observed.length, 1);
assert.equal(observed[0].authorization, 'Bearer opaque');
assert.deepEqual(observed[0].operation, {
operationId: 'run.get',
projectId: 'prj_default',
runId: 'run_123',
});
const listed = await request(
port,
'/api/v3/projects/prj_default/runs?limit=8&after_created_at_ms=100&after_run_id=run_100',
);
assert.equal(listed.statusCode, 200);
assert.deepEqual(listed.body, {
runs: [],
hasMore: false,
input: {
limit: 8,
after: { createdAtMs: 100, runId: 'run_100' },
},
});
assert.equal(observed[1].operation.operationId, 'run.list');
const events = await request(
port,
'/api/v3/projects/prj_default/runs/run_123/events?after_sequence=7&limit=8',
);
assert.deepEqual(events.body, {
events: [],
hasMore: false,
nextAfterSequence: 7,
});
assert.deepEqual(observed[2].operation, {
operationId: 'run.events.list',
projectId: 'prj_default',
runId: 'run_123',
input: { afterSequence: 7, limit: 8 },
});
const steps = await request(
port,
'/api/v3/projects/prj_default/runs/run_123/steps?after_step_key=build&after_step_run_id=step_1&limit=8',
);
assert.deepEqual(steps.body, {
steps: [],
hasMore: false,
next: { stepKey: 'build', stepRunId: 'step_1' },
});
assert.deepEqual(observed[3].operation, {
operationId: 'run.steps.list',
projectId: 'prj_default',
runId: 'run_123',
input: {
after: { stepKey: 'build', stepRunId: 'step_1' },
limit: 8,
},
});
const tasks = await request(
port,
'/api/v3/projects/prj_default/tasks?after_task_id=task_100&limit=8',
);
assert.deepEqual(tasks.body, {
tasks: [],
hasMore: false,
input: { after: { taskId: 'task_100' }, limit: 8 },
});
assert.deepEqual(observed[4].operation, {
operationId: 'task.list',
projectId: 'prj_default',
input: { after: { taskId: 'task_100' }, limit: 8 },
});
const task = await request(
port,
'/api/v3/projects/prj_default/tasks/task_1',
);
assert.deepEqual(task.body, { task: { taskId: 'task_1' } });
assert.deepEqual(observed[5].operation, {
operationId: 'task.get',
projectId: 'prj_default',
taskId: 'task_1',
});
const cancellationBody = JSON.stringify({
schema: 'qinglong/run-cancellation@v1',
mutationId: 'mutation-1',
});
const cancellation = await request(
port,
'/api/v3/projects/prj_default/runs/run_123/cancellation',
{
method: 'POST',
headers: {
authorization: 'Bearer opaque',
'content-type': 'application/json',
'content-length': String(Buffer.byteLength(cancellationBody)),
},
body: cancellationBody,
},
);
assert.equal(cancellation.statusCode, 202);
assert.deepEqual(cancellation.body.accepted, JSON.parse(cancellationBody));
assert.deepEqual(observed[6].operation, {
operationId: 'run.cancel',
projectId: 'prj_default',
runId: 'run_123',
});
const taskStartBody = JSON.stringify({
schema: 'qinglong/task-start@v1',
mutationId: '019f7300-0000-7000-8000-000000000800',
expectedRevision: 7,
expectedContentDigest: 'a'.repeat(64),
});
const taskStart = await request(
port,
'/api/v3/projects/prj_default/tasks/task_1/runs',
{
method: 'POST',
headers: {
authorization: 'Bearer opaque',
'content-type': 'application/json',
'content-length': String(Buffer.byteLength(taskStartBody)),
},
body: taskStartBody,
},
);
assert.equal(taskStart.statusCode, 202);
assert.deepEqual(taskStart.body.accepted, JSON.parse(taskStartBody));
assert.deepEqual(observed[7].operation, {
operationId: 'task.start',
projectId: 'prj_default',
taskId: 'task_1',
});
for (const invalidPath of [
'/api/v3/projects/prj_default/runs/run_123?expanded=true',
'/api/v3/projects/prj_default/runs/run%5f123',
'/api/v3/projects/prj_default/tasks/task_1?expanded=true',
'/api/v3/projects/prj_default/tasks/task%5f1',
'/api/v3/projects/prj_default/tasks/task_1/runs?expanded=true',
'/api/v3/projects/prj_default/tasks?after_task_id=%74ask_1',
]) {
assert.deepEqual((await request(port, invalidPath)).body, {
code: 'route_not_found',
});
}
for (const invalidQuery of [
'/api/v3/projects/prj_default/runs?',
'/api/v3/projects/prj_default/runs?limit=08',
'/api/v3/projects/prj_default/runs?limit=65',
'/api/v3/projects/prj_default/runs?limit=8&limit=9',
'/api/v3/projects/prj_default/runs?after_run_id=run_100',
'/api/v3/projects/prj_default/runs?unknown=value',
]) {
const invalid = await request(port, invalidQuery);
assert.equal(invalid.statusCode, 400);
assert.deepEqual(invalid.body, { code: 'invalid_run_list_query' });
}
for (const invalidQuery of [
'/api/v3/projects/prj_default/tasks?',
'/api/v3/projects/prj_default/tasks?limit=08',
'/api/v3/projects/prj_default/tasks?limit=65',
'/api/v3/projects/prj_default/tasks?unknown=value',
]) {
const invalid = await request(port, invalidQuery);
assert.equal(invalid.statusCode, 400);
assert.deepEqual(invalid.body, { code: 'invalid_task_list_query' });
}
for (const invalidQuery of [
'/api/v3/projects/prj_default/runs/run_123/events?',
'/api/v3/projects/prj_default/runs/run_123/events?after_sequence=07',
'/api/v3/projects/prj_default/runs/run_123/events?after_sequence=-1',
'/api/v3/projects/prj_default/runs/run_123/events?limit=65',
'/api/v3/projects/prj_default/runs/run_123/events?unknown=value',
]) {
const invalid = await request(port, invalidQuery);
assert.equal(invalid.statusCode, 400);
assert.deepEqual(invalid.body, { code: 'invalid_run_event_list_query' });
}
for (const invalidQuery of [
'/api/v3/projects/prj_default/runs/run_123/steps?',
'/api/v3/projects/prj_default/runs/run_123/steps?after_step_key=build',
'/api/v3/projects/prj_default/runs/run_123/steps?after_step_run_id=step_1',
'/api/v3/projects/prj_default/runs/run_123/steps?after_step_key=-bad&after_step_run_id=step_1',
'/api/v3/projects/prj_default/runs/run_123/steps?limit=65',
'/api/v3/projects/prj_default/runs/run_123/steps?unknown=value',
]) {
const invalid = await request(port, invalidQuery);
assert.equal(invalid.statusCode, 400);
assert.deepEqual(invalid.body, { code: 'invalid_run_step_list_query' });
}
assert.equal(observed.length, 8);
assert.deepEqual(
await Promise.all([surface.stopAndDrain(), surface.stopAndDrain()]),
['stopped', 'stopped'],
);
});
test('rejects GET bodies without invoking the prepared route handler', async (t) => {
const port = await reservePort();
let handlers = 0;
const surface = await startLocalApiHttpSurface({
profile: 'standalone',
host: '127.0.0.1',
port,
admission: preparedAdmission(async () => {
handlers += 1;
return { statusCode: 200, body: {} };
}),
});
t.after(() => surface.stopAndDrain());
const response = await request(
port,
'/api/v3/projects/prj_default/runs/run_123',
{
headers: {
authorization: 'Bearer opaque',
'content-length': '1',
},
body: 'x',
},
);
assert.equal(response.statusCode, 400);
assert.deepEqual(response.body, { code: 'invalid_request_body' });
assert.equal(handlers, 0);
});
test('authenticates before reading a strictly bounded cancellation JSON body', async (t) => {
const port = await reservePort();
const events = [];
const surface = await startLocalApiHttpSurface({
profile: 'edge',
host: '127.0.0.1',
port,
admission: {
async prepare(value) {
events.push(`prepare:${value.operation.operationId}`);
return {
bodyMode: 'json',
maximumBodyBytes: 512,
handle(body) {
events.push(`handle:${body.mutationId}`);
return { statusCode: 202, body };
},
};
},
},
});
t.after(() => surface.stopAndDrain());
const body = JSON.stringify({
schema: 'qinglong/run-cancellation@v1',
mutationId: 'mutation-1',
});
const accepted = await request(
port,
'/api/v3/projects/prj_default/runs/run_123/cancellation',
{
method: 'POST',
headers: {
authorization: 'Bearer opaque',
'content-type': 'application/json',
'content-length': String(Buffer.byteLength(body)),
},
body,
},
);
assert.equal(accepted.statusCode, 202);
assert.deepEqual(events, ['prepare:run.cancel', 'handle:mutation-1']);
for (const [headers, payload, statusCode, code] of [
[
{
authorization: 'Bearer opaque',
'content-type': 'text/plain',
'content-length': String(Buffer.byteLength(body)),
},
body,
400,
'invalid_request_body',
],
[
{
authorization: 'Bearer opaque',
'content-type': 'application/json',
'content-length': '513',
},
'x'.repeat(513),
413,
'request_body_too_large',
],
[
{
authorization: 'Bearer opaque',
'content-type': 'application/json',
'content-length': '1',
},
'{',
400,
'invalid_request_body',
],
]) {
const rejected = await request(
port,
'/api/v3/projects/prj_default/runs/run_123/cancellation',
{ method: 'POST', headers, body: payload },
);
assert.equal(rejected.statusCode, statusCode);
assert.deepEqual(rejected.body, { code });
}
assert.equal(events.filter((event) => event.startsWith('handle:')).length, 1);
});
test('serves the reviewed worst-case 64-item Run list inside the fixed response cap', async (t) => {
const port = await reservePort();
const text255 = 'x'.repeat(255);
const id128 = 'x'.repeat(128);
const item = Object.freeze({
id: id128,
taskId: text255,
taskRevision: text255,
status: 'succeeded',
version: 2_147_483_647,
eventSequence: 2_147_483_647,
priority: -2_147_483_648,
executionOrigin: 'scheduled_system',
executionOwner: 'runtime',
createdAtMs: Number.MAX_SAFE_INTEGER,
queuedAtMs: Number.MAX_SAFE_INTEGER,
startedAtMs: Number.MAX_SAFE_INTEGER,
finishedAtMs: Number.MAX_SAFE_INTEGER,
});
const surface = await startLocalApiHttpSurface({
profile: 'edge',
host: '127.0.0.1',
port,
admission: preparedAdmission(async () => {
return {
statusCode: 200,
body: {
runs: Object.freeze(Array.from({ length: 64 }, () => item)),
hasMore: true,
next: { createdAtMs: Number.MAX_SAFE_INTEGER, runId: id128 },
},
};
}),
});
t.after(() => surface.stopAndDrain());
const response = await request(
port,
'/api/v3/projects/prj_default/runs?limit=64',
);
assert.equal(response.statusCode, 200);
assert.equal(response.body.runs.length, 64);
assert.equal(Number(response.headers['content-length']), 61_516);
});
test('serves the reviewed worst-case 64-item Task list inside the fixed response cap', async (t) => {
const port = await reservePort();
const id128 = 'x'.repeat(128);
const item = Object.freeze({
taskId: id128,
revision: 2_147_483_647,
name: 'x'.repeat(255),
kind: 'workflow',
specSchema: `${'x'.repeat(64)}/${'x'.repeat(64)}@v999999`,
enabled: false,
updatedAtMs: Number.MAX_SAFE_INTEGER,
});
const surface = await startLocalApiHttpSurface({
profile: 'edge',
host: '127.0.0.1',
port,
admission: preparedAdmission(async () => ({
statusCode: 200,
body: {
tasks: Object.freeze(Array.from({ length: 64 }, () => item)),
hasMore: true,
next: { taskId: id128 },
},
})),
});
t.after(() => surface.stopAndDrain());
const response = await request(
port,
'/api/v3/projects/prj_default/tasks?limit=64',
);
assert.equal(response.statusCode, 200);
assert.equal(response.body.tasks.length, 64);
assert.ok(Number(response.headers['content-length']) < 64 * 1_024);
});
test('serves the reviewed worst-case 64-item RunEvent list inside the fixed response cap', async (t) => {
const port = await reservePort();
const event = Object.freeze({
sequence: 2_147_483_647,
type: 'x'.repeat(128),
actorType: 'system',
createdAtMs: Number.MAX_SAFE_INTEGER,
});
const surface = await startLocalApiHttpSurface({
profile: 'edge',
host: '127.0.0.1',
port,
admission: preparedAdmission(async () => {
return {
statusCode: 200,
body: {
events: Object.freeze(Array.from({ length: 64 }, () => event)),
hasMore: true,
nextAfterSequence: event.sequence,
},
};
}),
});
t.after(() => surface.stopAndDrain());
const response = await request(
port,
'/api/v3/projects/prj_default/runs/run_123/events?limit=64',
);
assert.equal(response.statusCode, 200);
assert.equal(response.body.events.length, 64);
assert.equal(Number(response.headers['content-length']), 13_754);
});
test('serves the reviewed worst-case 64-item Run Step list inside the fixed response cap', async (t) => {
const port = await reservePort();
const id128 = 'x'.repeat(128);
const item = Object.freeze({
id: id128,
parentStepRunId: id128,
stepKey: id128,
kind: 'tool',
required: true,
status: 'waiting_approval',
version: 2_147_483_647,
attemptCount: 64,
readyAtMs: Number.MAX_SAFE_INTEGER,
startedAtMs: Number.MAX_SAFE_INTEGER,
finishedAtMs: Number.MAX_SAFE_INTEGER,
resultCode: 'x'.repeat(64),
createdAtMs: Number.MAX_SAFE_INTEGER,
updatedAtMs: Number.MAX_SAFE_INTEGER,
});
const surface = await startLocalApiHttpSurface({
profile: 'edge',
host: '127.0.0.1',
port,
admission: preparedAdmission(async () => {
return {
statusCode: 200,
body: {
steps: Object.freeze(Array.from({ length: 64 }, () => item)),
hasMore: true,
next: { stepKey: id128, stepRunId: id128 },
},
};
}),
});
t.after(() => surface.stopAndDrain());
const response = await request(
port,
'/api/v3/projects/prj_default/runs/run_123/steps?limit=64',
);
assert.equal(response.statusCode, 200);
assert.equal(response.body.steps.length, 64);
assert.ok(Number(response.headers['content-length']) < 65_536);
});
test('bounds Edge admission concurrency and drains accepted work', async (t) => {
const port = await reservePort();
let admissions = 0;
let release;
const barrier = new Promise((resolve) => {
release = resolve;
});
const surface = await startLocalApiHttpSurface({
profile: 'edge',
host: '127.0.0.1',
port,
admission: preparedAdmission(async (value) => {
admissions += 1;
await barrier;
return {
statusCode: 200,
body: { run: { id: value.operation.runId } },
};
}),
});
t.after(() => surface.stopAndDrain());
const accepted = Array.from({ length: 4 }, () =>
request(port, '/api/v3/projects/prj_default/runs/run_123'),
);
while (admissions < 4) await new Promise((resolve) => setImmediate(resolve));
const overloaded = await request(
port,
'/api/v3/projects/prj_default/runs/run_123',
);
assert.equal(overloaded.statusCode, 503);
assert.deepEqual(overloaded.body, { code: 'server_overloaded' });
assert.equal(admissions, 4);
const stopping = surface.stopAndDrain();
release();
assert.deepEqual(
(await Promise.all(accepted)).map(({ statusCode }) => statusCode),
[200, 200, 200, 200],
);
assert.equal(await stopping, 'stopped');
});
@@ -0,0 +1,51 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
LOCAL_API_PROCESS_CONFIG_SCHEMA,
} = require('../dist/production-process/config.js');
const {
runProductionLocalApiProcess,
} = require('../dist/production-process/processApplication.js');
test('injects the HTTP surface into exactly one Local Application process', async () => {
const events = [];
const signals = Object.freeze({ subscribe() { return () => {}; } });
const emit = (event) => events.push(event);
let applicationCalls = 0;
const result = await runProductionLocalApiProcess(
{
configFilePath: '/srv/qinglong/private/api.json',
signals,
emit,
},
{
readConfig(configFilePath) {
assert.equal(configFilePath, '/srv/qinglong/private/api.json');
return Object.freeze({
schema: LOCAL_API_PROCESS_CONFIG_SCHEMA,
deploymentRoot: '/srv/qinglong',
applicationConfigFilePath:
'/srv/qinglong/private/application.json',
ownerPepperKeyringDirectory:
'/srv/qinglong/private/owner-pepper',
listener: Object.freeze({ host: '127.0.0.1', port: 5701 }),
});
},
async runApplication(options) {
applicationCalls += 1;
assert.equal(
options.configFilePath,
'/srv/qinglong/private/application.json',
);
assert.equal(options.signals, signals);
assert.equal(options.emit, emit);
assert.equal(typeof options.productSurface.start, 'function');
return 'stopped';
},
},
);
assert.equal(result, 'stopped');
assert.equal(applicationCalls, 1);
assert.deepEqual(events, []);
});
@@ -0,0 +1,137 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
RUN_CANCELLATION_SCHEMA,
RunCancellationFenceRejectedError,
RunCancellationNotFoundError,
RunCancellationUnavailableError,
} = require('@qinglong/runtime-core/run-cancellation');
const {
createLocalApiRunCancellationRoute,
} = require('../dist/run/runCancellationRoute.js');
const PRINCIPAL = Object.freeze({
subject: Object.freeze({ type: 'user', id: 'user-1' }),
authenticationId: 'credential:user-1',
authenticatedAtMs: 9_000,
expiresAtMs: 11_000,
assurance: 'single_factor',
});
const FENCE = Object.freeze({ projectVersion: 2, bindingVersion: 3 });
function accepted(overrides = {}) {
return {
status: 'accepted',
projectId: 'project-1',
runId: 'run-1',
runStatus: 'running',
runVersion: 5,
eventSequence: 7,
cancelRequestedAtMs: 10_000,
cancelReason: 'user',
...overrides,
};
}
function request(overrides = {}) {
return {
projectId: 'project-1',
runId: 'run-1',
body: { schema: RUN_CANCELLATION_SCHEMA, mutationId: 'mutation-1' },
principal: PRINCIPAL,
policyFence: FENCE,
...overrides,
};
}
test('publishes one profile-neutral durable cancellation command', async () => {
let observed;
const route = createLocalApiRunCancellationRoute(
{
async requestUserCancellation(command) {
observed = command;
return accepted();
},
},
() => '018f0000-0000-7000-8000-000000000001',
);
assert.deepEqual(await route.handle(request()), {
statusCode: 202,
body: { schema: RUN_CANCELLATION_SCHEMA, ...accepted() },
});
assert.deepEqual(observed, {
projectId: 'project-1',
runId: 'run-1',
mutationId: 'mutation-1',
eventId: '018f0000-0000-7000-8000-000000000001',
subject: PRINCIPAL.subject,
policyFence: FENCE,
});
});
test('rejects malformed bodies and incomplete authorization fences', async () => {
let calls = 0;
const route = createLocalApiRunCancellationRoute(
{
async requestUserCancellation() {
calls += 1;
return accepted();
},
},
() => '018f0000-0000-7000-8000-000000000001',
);
assert.deepEqual(await route.handle(request({ body: { mutationId: 'x' } })), {
statusCode: 400,
body: {
code: 'invalid_run_cancellation_request',
schema: RUN_CANCELLATION_SCHEMA,
},
});
assert.deepEqual(await route.handle(request({ policyFence: null })), {
statusCode: 503,
body: { code: 'run_cancellation_unavailable' },
});
assert.equal(calls, 0);
});
test('maps replay, terminal, missing, fence and unavailable outcomes', async () => {
for (const outcome of [
accepted({ status: 'already_requested' }),
{
status: 'already_terminal',
projectId: 'project-1',
runId: 'run-1',
runStatus: 'succeeded',
runVersion: 6,
eventSequence: 8,
},
]) {
const route = createLocalApiRunCancellationRoute(
{ async requestUserCancellation() { return outcome; } },
() => '018f0000-0000-7000-8000-000000000001',
);
assert.equal((await route.handle(request())).statusCode, 200);
}
for (const [error, statusCode, code] of [
[new RunCancellationNotFoundError(), 404, 'run_not_found'],
[
new RunCancellationFenceRejectedError('authorization_changed'),
409,
'run_cancellation_fence_rejected',
],
[
new RunCancellationUnavailableError(),
503,
'run_cancellation_unavailable',
],
]) {
const route = createLocalApiRunCancellationRoute(
{ async requestUserCancellation() { throw error; } },
() => '018f0000-0000-7000-8000-000000000001',
);
const response = await route.handle(request());
assert.equal(response.statusCode, statusCode);
assert.equal(response.body.code, code);
}
});
@@ -0,0 +1,98 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createLocalApiRunEventListRoute,
} = require('../dist/run/runEventListRoute.js');
function run(projectId = 'prj_default') {
return { id: 'run-1', projectId };
}
function event(sequence, overrides = {}) {
return {
id: `event-${sequence}`,
runId: 'run-1',
sequence,
type: `run.event.${sequence}`,
actorType: 'system',
actorId: 'private-actor',
dedupeKey: 'private-dedupe',
payload: { secret: 'must-not-cross-projection' },
createdAtMs: 1_000 + sequence,
...overrides,
};
}
test('returns the shared bounded Run event projection', async () => {
const calls = [];
const route = createLocalApiRunEventListRoute({
async findRunById(runId) {
calls.push(['run', runId]);
return run();
},
async listEvents(runId, input) {
calls.push(['events', runId, input]);
return [event(3), event(4)];
},
});
const response = await route.handle({
projectId: 'prj_default',
runId: 'run-1',
input: { afterSequence: 2, limit: 1 },
});
assert.deepEqual(calls, [
['run', 'run-1'],
['events', 'run-1', { afterSequence: 2, limit: 2 }],
]);
assert.deepEqual(response, {
statusCode: 200,
body: {
events: [
{
sequence: 3,
type: 'run.event.3',
actorType: 'system',
createdAtMs: 1_003,
},
],
hasMore: true,
nextAfterSequence: 3,
},
});
assert.equal(JSON.stringify(response).includes('private'), false);
assert.equal(JSON.stringify(response).includes('secret'), false);
});
test('masks absent and cross-Project Runs and fails closed on corrupt storage', async () => {
for (const value of [null, run('prj_other')]) {
const route = createLocalApiRunEventListRoute({
async findRunById() {
return value;
},
async listEvents() {
throw new Error('must not read');
},
});
assert.deepEqual(
await route.handle({
projectId: 'prj_default',
runId: 'run-1',
input: {},
}),
{ statusCode: 404, body: { code: 'run_not_found' } },
);
}
const route = createLocalApiRunEventListRoute({
async findRunById() {
return run();
},
async listEvents() {
return [event(2), event(1)];
},
});
assert.deepEqual(
await route.handle({ projectId: 'prj_default', runId: 'run-1', input: {} }),
{ statusCode: 503, body: { code: 'run_event_list_unavailable' } },
);
});
@@ -0,0 +1,67 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createLocalApiRunListRoute,
} = require('../dist/run/runListRoute.js');
function run(id, createdAtMs, overrides = {}) {
return {
id,
projectId: 'prj_default',
taskId: `task-${id}`,
taskRevision: 'revision-1',
triggerType: 'manual',
executionOrigin: 'manual',
executionOwner: 'runtime',
status: 'running',
version: 0,
eventSequence: 1,
priority: 0,
createdAtMs,
privateValue: 'secret-adjacent',
...overrides,
};
}
test('returns the shared bounded Project Run list projection', async () => {
const calls = [];
const route = createLocalApiRunListRoute({
async listRunsByProject(query) {
calls.push(query);
return [run('run-b', 20), run('run-a', 10)];
},
});
const response = await route.handle({
projectId: 'prj_default',
input: { limit: 1 },
});
assert.deepEqual(calls, [{ projectId: 'prj_default', limit: 2 }]);
assert.equal(response.statusCode, 200);
assert.equal(response.body.runs[0].id, 'run-b');
assert.equal(response.body.hasMore, true);
assert.deepEqual(response.body.next, { createdAtMs: 20, runId: 'run-b' });
assert.equal(JSON.stringify(response).includes('secret-adjacent'), false);
});
test('fails closed on cross-Project, malformed and unavailable pages', async () => {
for (const rows of [
[run('run-a', 10, { projectId: 'prj_other' })],
[run('run-a', 10, { status: 'invented' })],
]) {
const route = createLocalApiRunListRoute({
async listRunsByProject() { return rows; },
});
assert.deepEqual(
await route.handle({ projectId: 'prj_default', input: {} }),
{ statusCode: 503, body: { code: 'run_list_unavailable' } },
);
}
const unavailable = createLocalApiRunListRoute({
async listRunsByProject() { throw new Error('offline'); },
});
assert.deepEqual(
await unavailable.handle({ projectId: 'prj_default', input: {} }),
{ statusCode: 503, body: { code: 'run_list_unavailable' } },
);
});
@@ -0,0 +1,75 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createLocalApiRunReadRoute,
} = require('../dist/run/runReadRoute.js');
function run(overrides = {}) {
return {
id: 'run_123',
projectId: 'prj_default',
taskId: 'task_1',
taskRevision: 'revision_7',
taskName: 'must not cross the wire',
taskSnapshotRef: 'secret-adjacent-ref',
triggerType: 'manual',
executionOrigin: 'manual',
executionOwner: 'runtime',
triggeredBy: 'private-user-id',
requestId: 'private-request-id',
status: 'running',
version: 4,
eventSequence: 6,
priority: 10,
inputRef: 'private-input-ref',
outputRef: 'private-output-ref',
createdAtMs: 1_000,
queuedAtMs: 2_000,
startedAtMs: 3_000,
errorCode: 'private-error-code',
errorSummary: 'private error detail',
...overrides,
};
}
test('returns the shared bounded Run projection without secret-adjacent fields', async () => {
const route = createLocalApiRunReadRoute({
async findRunById(runId) {
assert.equal(runId, 'run_123');
return run({ version: 0 });
},
});
const response = await route.handle({
projectId: 'prj_default',
runId: 'run_123',
});
assert.equal(response.statusCode, 200);
assert.equal(response.body.run.projectId, 'prj_default');
assert.equal(response.body.run.version, 0);
assert.equal(JSON.stringify(response).includes('private'), false);
});
test('collapses absent and cross-project Runs and fails closed on repository errors', async () => {
for (const value of [null, run({ projectId: 'another_project' })]) {
const route = createLocalApiRunReadRoute({
async findRunById() {
return value;
},
});
assert.deepEqual(
await route.handle({ projectId: 'prj_default', runId: 'run_123' }),
{ statusCode: 404, body: { code: 'run_not_found' } },
);
}
const unavailable = createLocalApiRunReadRoute({
async findRunById() {
throw new Error('database unavailable');
},
});
assert.deepEqual(
await unavailable.handle({ projectId: 'prj_default', runId: 'run_123' }),
{ statusCode: 503, body: { code: 'run_query_unavailable' } },
);
});
@@ -0,0 +1,128 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createLocalApiRunStepListRoute,
} = require('../dist/run/runStepListRoute.js');
const {
createStepRunRecord,
} = require('../../ql3-runtime-core/dist/run/stepRun.js');
function run(projectId = 'prj_default') {
return { id: 'run-1', projectId };
}
function step(id, stepKey) {
return createStepRunRecord({
id,
runId: 'run-1',
parentStepRunId: 'step-parent',
stepKey,
kind: 'tool',
definitionRef: 'tool:private.internal@1.0.0',
definitionDigest: 'a'.repeat(64),
required: true,
initialStatus: 'ready',
inputRef: 'artifact:private-input',
mutationId: `create-${id}`,
createdAtMs: 1_000,
});
}
test('returns the shared bounded low-sensitive Run Step projection', async () => {
const calls = [];
const first = step('step-1', 'build');
const second = step('step-2', 'deploy');
const route = createLocalApiRunStepListRoute(
{
async findRunById(runId) {
calls.push(['run', runId]);
return run();
},
},
{
async listByRun(query) {
calls.push(['steps', query]);
return {
stepRuns: [first, second],
truncated: true,
next: { stepKey: second.stepKey, id: second.id },
};
},
},
);
const response = await route.handle({
projectId: 'prj_default',
runId: 'run-1',
input: {
after: { stepKey: 'admit', stepRunId: 'step-0' },
limit: 2,
},
});
assert.deepEqual(calls, [
['run', 'run-1'],
[
'steps',
{
runId: 'run-1',
limit: 2,
after: { stepKey: 'admit', id: 'step-0' },
},
],
]);
assert.equal(response.statusCode, 200);
assert.equal(response.body.steps.length, 2);
assert.deepEqual(response.body.next, {
stepKey: 'deploy',
stepRunId: 'step-2',
});
assert.equal(response.body.hasMore, true);
const serialized = JSON.stringify(response);
assert.equal(serialized.includes('private.internal'), false);
assert.equal(serialized.includes('private-input'), false);
assert.equal(serialized.includes('stepRunDigest'), false);
});
test('masks absent and cross-Project Runs and fails closed on corrupt storage', async () => {
for (const value of [null, run('prj_other')]) {
const route = createLocalApiRunStepListRoute(
{
async findRunById() {
return value;
},
},
{
async listByRun() {
throw new Error('must not read');
},
},
);
assert.deepEqual(
await route.handle({
projectId: 'prj_default',
runId: 'run-1',
input: {},
}),
{ statusCode: 404, body: { code: 'run_not_found' } },
);
}
const route = createLocalApiRunStepListRoute(
{
async findRunById() {
return run();
},
},
{
async listByRun() {
return {
stepRuns: [step('step-2', 'deploy'), step('step-1', 'build')],
truncated: false,
};
},
},
);
assert.deepEqual(
await route.handle({ projectId: 'prj_default', runId: 'run-1', input: {} }),
{ statusCode: 503, body: { code: 'run_step_list_unavailable' } },
);
});
@@ -0,0 +1,688 @@
const assert = require('node:assert/strict');
const http = require('node:http');
const fs = require('node:fs');
const net = require('node:net');
const os = require('node:os');
const path = require('node:path');
const { DatabaseSync } = require('node:sqlite');
const { test } = require('node:test');
const {
LocalOwnerPepperKeyringFileProvider,
provisionLocalOwnerPepperKey,
} = require('@qinglong/local-owner-console/pepper-custody');
const {
apiCredentialSecretDigest,
formatApiCredentialToken,
} = require('@qinglong/runtime-core/api-credential-token');
const {
createStepRunRecord,
} = require('../../ql3-runtime-core/dist/run/stepRun.js');
const {
createTaskDefinitionRecord,
} = require('@qinglong/runtime-core/task-definition');
const {
compileLocalCommandTaskDefinition,
} = require('@qinglong/runtime-core/task-definition-execution-compiler');
const {
createBuiltInTaskSpecSemanticRegistry,
} = require('@qinglong/runtime-core/task-spec-semantic');
const { migrateLocalSqlitePath } = require('@qinglong/local-sqlite/migration');
const {
openLocalSqliteRuntimeDatabase,
} = require('@qinglong/local-sqlite/runtime');
const {
createLocalApiProductSurface,
} = require('../dist/application-runtime/localApiProductSurface.js');
const NOW = 1_800_000_000_000;
const PEPPER_KEY_ID = 'local-api-pepper-v1';
const CREDENTIAL_ID = 'local-api-owner';
const RUN_ID = 'run_local_api_1';
const SECRET = Buffer.alloc(32, 81).toString('base64url');
const PEPPER = Buffer.alloc(32, 82).toString('base64url');
const TOKEN = formatApiCredentialToken(CREDENTIAL_ID, SECRET);
function reservePort() {
return new Promise((resolve, reject) => {
const server = net.createServer();
server.once('error', reject);
server.listen(0, '127.0.0.1', () => {
const address = server.address();
server.close((error) => {
if (error) reject(error);
else resolve(address.port);
});
});
});
}
function request(
port,
authorization,
requestPath = `/api/v3/projects/default/runs/${RUN_ID}`,
options = {},
) {
return new Promise((resolve, reject) => {
const outgoing = http.request(
{
host: '127.0.0.1',
port,
path: requestPath,
method: options.method ?? 'GET',
headers: {
authorization,
connection: 'close',
...(options.headers ?? {}),
},
},
(response) => {
const chunks = [];
response.on('data', (chunk) => chunks.push(chunk));
response.on('end', () =>
resolve({
statusCode: response.statusCode,
body: JSON.parse(Buffer.concat(chunks).toString('utf8')),
}),
);
},
);
outgoing.once('error', reject);
if (options.body) outgoing.write(options.body);
outgoing.end();
});
}
function seed(databasePath, materialDigest) {
const stepRun = createStepRunRecord({
id: 'step-local-api-1',
runId: RUN_ID,
stepKey: 'build',
kind: 'tool',
definitionRef: 'tool:private.internal@1.0.0',
definitionDigest: 'a'.repeat(64),
required: true,
initialStatus: 'ready',
inputRef: 'artifact:private-input',
mutationId: 'create-step-local-api-1',
createdAtMs: NOW - 75,
});
const client = new DatabaseSync(databasePath);
try {
client.exec('PRAGMA foreign_keys = ON');
client
.prepare(
`INSERT INTO "QingLong3LocalOwnerPepperKeys" (
"pepper_key_id", "material_digest", "backup_digest", "state",
"version", "register_mutation_id", "activate_mutation_id",
"registered_at_ms", "activated_at_ms"
) VALUES (?, ?, ?, 'active', 2, ?, ?, ?, ?)`,
)
.run(
PEPPER_KEY_ID,
materialDigest,
'b'.repeat(64),
'00000000-0000-4000-8000-000000000111',
'00000000-0000-4000-8000-000000000112',
NOW - 2_000,
NOW - 1_500,
);
client
.prepare(
`INSERT INTO "QingLong3LocalOwnerPepperActivations" (
"generation", "mutation_id", "expected_generation",
"previous_pepper_key_id", "active_pepper_key_id",
"material_digest", "backup_digest", "activated_at_ms"
) VALUES (1, ?, 0, NULL, ?, ?, ?, ?)`,
)
.run(
'00000000-0000-4000-8000-000000000112',
PEPPER_KEY_ID,
materialDigest,
'b'.repeat(64),
NOW - 1_500,
);
client
.prepare(
`INSERT INTO "QingLong3IdentitySubjects" (
"subject_type", "subject_id", "status", "version",
"created_at_ms", "updated_at_ms"
) VALUES ('user', 'local-api-user', 'active', 1, ?, ?)`,
)
.run(NOW - 1_000, NOW - 1_000);
client
.prepare(
`INSERT INTO "QingLong3ApiCredentials" (
"credential_id", "version", "state", "subject_type",
"subject_id", "secret_digest", "created_at_ms",
"not_before_at_ms", "expires_at_ms"
) VALUES (?, 1, 'active', 'user', 'local-api-user', ?, ?, ?, ?)`,
)
.run(
CREDENTIAL_ID,
apiCredentialSecretDigest(PEPPER, CREDENTIAL_ID, SECRET),
NOW - 1_000,
NOW - 1_000,
NOW + 60_000,
);
client
.prepare(
`INSERT INTO "QingLong3ApiCredentialPepperBindings" (
"credential_id", "credential_version", "pepper_key_id"
) VALUES (?, 1, ?)`,
)
.run(CREDENTIAL_ID, PEPPER_KEY_ID);
client
.prepare(
`INSERT INTO "QingLong3ProjectRoleBindings" (
"project_id", "subject_type", "subject_id", "version", "state",
"role", "mutation_id", "changed_by_type", "changed_by_id",
"created_at_ms"
) VALUES (
'default', 'user', 'local-api-user', 1, 'active', 'operator',
'grant-local-api-operator', 'user', 'local-api-user', ?
)`,
)
.run(NOW - 500);
const taskSemantics = createBuiltInTaskSpecSemanticRegistry();
const taskCommand = {
projectId: 'default',
taskId: 'task-1',
expectedRevision: null,
mutationId: '00000000-0000-4000-8000-000000000113',
name: 'Local API Task',
kind: 'command',
spec: {
schema: 'qinglong/command@v1',
config: {
command: {
kind: 'argv',
file: '/bin/echo',
args: ['private-command'],
},
},
},
labels: { private: 'label' },
enabled: true,
occurredAtMs: NOW - 200,
};
const taskDefinition = createTaskDefinitionRecord({
...taskCommand,
spec: taskSemantics.normalize({
projectId: taskCommand.projectId,
taskId: taskCommand.taskId,
kind: taskCommand.kind,
spec: taskCommand.spec,
}),
}, NOW - 200);
const taskExecution = compileLocalCommandTaskDefinition(
taskDefinition,
taskSemantics,
);
client
.prepare(
`INSERT INTO "QingLong3TaskDefinitions" (
"project_id", "task_id", "current_revision",
"created_at_ms", "updated_at_ms"
) VALUES (?, ?, ?, ?, ?)`,
)
.run(
taskDefinition.projectId,
taskDefinition.taskId,
taskDefinition.revision,
taskDefinition.createdAtMs,
taskDefinition.updatedAtMs,
);
client
.prepare(
`INSERT INTO "QingLong3LocalExecutionContextRecipes" (
"context_ref", "environment_json", "content_digest",
"created_at_ms"
) VALUES (?, ?, ?, ?)`,
)
.run(
taskExecution.contextRecipe.contextRef,
JSON.stringify(taskExecution.contextRecipe.environment),
taskExecution.contextRecipe.contentDigest,
taskExecution.contextRecipe.createdAtMs,
);
client
.prepare(
`INSERT INTO "QingLong3LocalTaskExecutionRevisions" (
"project_id", "task_id", "task_revision", "executor_type",
"command_json", "working_directory", "timeout_ms", "context_ref",
"content_digest", "created_at_ms"
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
taskExecution.executionRevision.projectId,
taskExecution.executionRevision.taskId,
taskExecution.executionRevision.taskRevision,
taskExecution.executionRevision.executorType,
JSON.stringify(taskExecution.executionRevision.command),
taskExecution.executionRevision.workingDirectory ?? null,
taskExecution.executionRevision.timeoutMs ?? null,
taskExecution.executionRevision.contextRef,
taskExecution.executionRevision.contentDigest,
taskExecution.executionRevision.createdAtMs,
);
client
.prepare(
`INSERT INTO "QingLong3TaskDefinitionRevisions" (
"project_id", "task_id", "revision", "mutation_id",
"name", "description", "kind", "spec_json", "labels_json",
"enabled", "content_digest", "created_at_ms"
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(
taskDefinition.projectId,
taskDefinition.taskId,
taskDefinition.revision,
taskDefinition.mutationId,
taskDefinition.name,
null,
taskDefinition.kind,
JSON.stringify(taskDefinition.spec),
JSON.stringify(taskDefinition.labels),
1,
taskDefinition.contentDigest,
taskDefinition.updatedAtMs,
);
client
.prepare(
`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 (?, 'default', 'task-1', 'revision-1', 'manual',
'manual', 'runtime', 'running', 1, 1, 0, ?)`,
)
.run(RUN_ID, NOW - 100);
client
.prepare(
`INSERT INTO "StepRuns" (
"id", "run_id", "parent_step_run_id", "step_key", "kind",
"definition_ref", "definition_digest", "required", "status",
"version", "attempt_count", "input_ref", "output_ref",
"approval_request_id", "ready_at_ms", "started_at_ms",
"finished_at_ms", "result_code", "error_summary", "created_at_ms",
"updated_at_ms", "last_mutation_id", "step_run_digest",
"step_run_json"
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?
)`,
)
.run(
stepRun.id,
stepRun.runId,
stepRun.parentStepRunId,
stepRun.stepKey,
stepRun.kind,
stepRun.definitionRef,
stepRun.definitionDigest,
stepRun.required ? 1 : 0,
stepRun.status,
stepRun.version,
stepRun.attemptCount,
stepRun.inputRef,
stepRun.outputRef,
stepRun.approvalRequestId,
stepRun.readyAtMs,
stepRun.startedAtMs,
stepRun.finishedAtMs,
stepRun.resultCode,
stepRun.errorSummary,
stepRun.createdAtMs,
stepRun.updatedAtMs,
stepRun.lastMutationId,
stepRun.stepRunDigest,
JSON.stringify(stepRun),
);
client
.prepare(
`INSERT INTO "RunEvents" (
id, run_id, sequence, type, actor_type, payload, created_at_ms
) VALUES (?, ?, 1, 'run.started', 'system', '{}', ?)`,
)
.run('run-local-api-event-1', RUN_ID, NOW - 50);
return taskDefinition;
} finally {
client.close();
}
}
test('serves an authenticated Run through one real SQLite authority and durable audit', async (t) => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-api-sqlite-'));
fs.chmodSync(root, 0o700);
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const databasePath = path.join(root, 'qinglong3.sqlite');
const keyringDirectory = path.join(root, 'owner-pepper');
fs.mkdirSync(keyringDirectory, { mode: 0o700 });
const summary = provisionLocalOwnerPepperKey({
keyringDirectory,
pepperKeyId: PEPPER_KEY_ID,
randomBytes: () => Buffer.alloc(32, 82),
});
await migrateLocalSqlitePath({ databasePath, profile: 'edge' });
const taskDefinition = seed(databasePath, summary.digest);
const runtime = await openLocalSqliteRuntimeDatabase({
databasePath,
profile: 'edge',
});
t.after(() => runtime.close());
const port = await reservePort();
let uuidSequence = 0;
const surface = createLocalApiProductSurface(
{
schema: 'qinglong/local-api-process@v1',
deploymentRoot: root,
applicationConfigFilePath: path.join(root, 'application.json'),
ownerPepperKeyringDirectory: keyringDirectory,
listener: { host: '127.0.0.1', port },
},
{
now: () => NOW,
randomUuid() {
uuidSequence += 1;
return `00000000-0000-4000-8000-${String(uuidSequence).padStart(
12,
'0',
)}`;
},
},
);
const active = await surface.start({
profile: 'edge',
runs: runtime.runRepository,
stepRuns: await runtime.stepRunReader(),
runCancellation: await runtime.runCancellationRepository(),
taskStart: await runtime.taskStartRepository(),
taskDefinitions: runtime.taskDefinitions,
apiCredentials: runtime.apiCredentials,
ownerPepper: runtime.ownerPepper,
projectPolicy: runtime.projectPolicy,
securityAudit: runtime.securityAudit,
});
t.after(() => active.stopAndDrain());
const accepted = await request(port, `Bearer ${TOKEN}`);
assert.equal(accepted.statusCode, 200);
assert.equal(accepted.body.run.id, RUN_ID);
assert.equal(accepted.body.run.projectId, 'default');
assert.equal(JSON.stringify(accepted).includes('secret'), false);
const listed = await request(
port,
`Bearer ${TOKEN}`,
'/api/v3/projects/default/runs?limit=1',
);
assert.equal(listed.statusCode, 200);
assert.equal(listed.body.runs[0].id, RUN_ID);
assert.equal(listed.body.hasMore, false);
assert.equal(JSON.stringify(listed).includes('secret'), false);
const tasks = await request(
port,
`Bearer ${TOKEN}`,
'/api/v3/projects/default/tasks?limit=1',
);
assert.deepEqual(tasks, {
statusCode: 200,
body: {
tasks: [
{
taskId: 'task-1',
revision: 1,
name: 'Local API Task',
kind: 'command',
specSchema: 'qinglong/command@v1',
enabled: true,
updatedAtMs: NOW - 200,
},
],
hasMore: false,
},
});
assert.equal(JSON.stringify(tasks).includes('private'), false);
const currentTask = await request(
port,
`Bearer ${TOKEN}`,
'/api/v3/projects/default/tasks/task-1',
);
assert.equal(currentTask.statusCode, 200);
assert.deepEqual(
{
...currentTask.body.task,
contentDigest: '<digest>',
},
{
taskId: 'task-1',
revision: 1,
name: 'Local API Task',
kind: 'command',
specSchema: 'qinglong/command@v1',
enabled: true,
contentDigest: '<digest>',
createdAtMs: NOW - 200,
updatedAtMs: NOW - 200,
},
);
assert.match(currentTask.body.task.contentDigest, /^[0-9a-f]{64}$/);
assert.equal(JSON.stringify(currentTask).includes('private'), false);
assert.deepEqual(
await request(
port,
`Bearer ${TOKEN}`,
'/api/v3/projects/default/tasks/task-absent',
),
{ statusCode: 404, body: { code: 'task_not_found' } },
);
const taskStartBody = JSON.stringify({
schema: 'qinglong/task-start@v1',
mutationId: '019f7300-0000-7000-8000-000000000800',
expectedRevision: taskDefinition.revision,
expectedContentDigest: taskDefinition.contentDigest,
});
const taskStartOptions = {
method: 'POST',
headers: {
'content-type': 'application/json',
'content-length': String(Buffer.byteLength(taskStartBody)),
},
body: taskStartBody,
};
const taskStartPath = '/api/v3/projects/default/tasks/task-1/runs';
const started = await request(
port,
`Bearer ${TOKEN}`,
taskStartPath,
taskStartOptions,
);
assert.equal(started.statusCode, 202);
assert.equal(started.body.schema, 'qinglong/task-start@v1');
assert.equal(started.body.status, 'accepted');
assert.equal(started.body.runStatus, 'queued');
assert.equal(started.body.executorType, 'local_process');
assert.equal(started.body.taskContentDigest, taskDefinition.contentDigest);
const taskStartReplay = await request(
port,
`Bearer ${TOKEN}`,
taskStartPath,
taskStartOptions,
);
assert.equal(taskStartReplay.statusCode, 200);
assert.equal(taskStartReplay.body.status, 'existing');
assert.equal(taskStartReplay.body.runId, started.body.runId);
assert.equal(taskStartReplay.body.attemptId, started.body.attemptId);
const timeline = await request(
port,
`Bearer ${TOKEN}`,
`/api/v3/projects/default/runs/${RUN_ID}/events?limit=1`,
);
assert.deepEqual(timeline, {
statusCode: 200,
body: {
events: [
{
sequence: 1,
type: 'run.started',
actorType: 'system',
createdAtMs: NOW - 50,
},
],
hasMore: false,
nextAfterSequence: 1,
},
});
assert.equal(JSON.stringify(timeline).includes('payload'), false);
const steps = await request(
port,
`Bearer ${TOKEN}`,
`/api/v3/projects/default/runs/${RUN_ID}/steps?limit=1`,
);
assert.deepEqual(steps, {
statusCode: 200,
body: {
steps: [
{
id: 'step-local-api-1',
parentStepRunId: null,
stepKey: 'build',
kind: 'tool',
required: true,
status: 'ready',
version: 1,
attemptCount: 0,
readyAtMs: NOW - 75,
startedAtMs: null,
finishedAtMs: null,
resultCode: null,
createdAtMs: NOW - 75,
updatedAtMs: NOW - 75,
},
],
hasMore: false,
next: null,
},
});
assert.equal(JSON.stringify(steps).includes('private'), false);
const cancellationBody = JSON.stringify({
schema: 'qinglong/run-cancellation@v1',
mutationId: 'cancel-local-api-1',
});
const cancellationPath =
`/api/v3/projects/default/runs/${RUN_ID}/cancellation`;
const cancellationOptions = {
method: 'POST',
headers: {
'content-type': 'application/json',
'content-length': String(Buffer.byteLength(cancellationBody)),
},
body: cancellationBody,
};
const cancelled = await request(
port,
`Bearer ${TOKEN}`,
cancellationPath,
cancellationOptions,
);
assert.equal(cancelled.statusCode, 202);
assert.equal(cancelled.body.schema, 'qinglong/run-cancellation@v1');
assert.equal(cancelled.body.status, 'accepted');
assert.equal(cancelled.body.cancelReason, 'user');
const replayed = await request(
port,
`Bearer ${TOKEN}`,
cancellationPath,
cancellationOptions,
);
assert.equal(replayed.statusCode, 200);
assert.equal(replayed.body.status, 'already_requested');
const wrongSecret = Buffer.alloc(32, 83).toString('base64url');
assert.deepEqual(
await request(
port,
`Bearer ${formatApiCredentialToken(CREDENTIAL_ID, wrongSecret)}`,
),
{ statusCode: 401, body: { code: 'authentication_required' } },
);
const auditReader = new DatabaseSync(databasePath, { readOnly: true });
try {
assert.deepEqual(
auditReader
.prepare(
`SELECT operation_id, outcome FROM "QingLong3SecurityAuditEvents"
WHERE operation_id IN (
'run.get', 'run.list', 'run.events.list', 'run.steps.list',
'run.cancel', 'task.get', 'task.list'
, 'task.start'
)
ORDER BY operation_id, outcome`,
)
.all()
.map(({ operation_id, outcome }) => `${operation_id}:${outcome}`),
[
'run.cancel:allowed',
'run.cancel:allowed',
'run.events.list:allowed',
'run.get:allowed',
'run.get:authentication_rejected',
'run.list:allowed',
'run.steps.list:allowed',
'task.get:allowed',
'task.get:allowed',
'task.list:allowed',
'task.start:allowed',
'task.start:allowed',
],
);
assert.deepEqual(
{
...auditReader
.prepare(
`SELECT "status", "version", "event_sequence" AS "eventSequence",
"trigger_type" AS "triggerType"
FROM "Runs" WHERE "id" = ?`,
)
.get(started.body.runId),
},
{
status: 'queued',
version: 2,
eventSequence: 2,
triggerType: 'task_start',
},
);
assert.deepEqual(
{
...auditReader
.prepare(
`SELECT "version", "event_sequence" AS "eventSequence",
"cancel_reason" AS "cancelReason"
FROM "Runs" WHERE "id" = ?`,
)
.get(RUN_ID),
},
{ version: 2, eventSequence: 2, cancelReason: 'user' },
);
assert.equal(
auditReader
.prepare(
`SELECT COUNT(*) AS count FROM "RunEvents"
WHERE "run_id" = ? AND "type" = 'run.cancel_requested'`,
)
.get(RUN_ID).count,
1,
);
} finally {
auditReader.close();
}
});
@@ -0,0 +1,93 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createLocalApiTaskListRoute,
} = require('../dist/task/taskListRoute.js');
function task(taskId, overrides = {}) {
return {
projectId: 'prj_default',
taskId,
revision: 2,
name: `Task ${taskId}`,
description: 'secret-adjacent',
kind: 'command',
spec: { schema: 'qinglong/command@v1', config: { command: ['private'] } },
labels: { private: 'value' },
enabled: true,
mutationId: 'mutation-private',
contentDigest: 'digest-private',
createdAtMs: 10,
updatedAtMs: 20,
...overrides,
};
}
test('returns the shared bounded Project Task list projection', async () => {
const calls = [];
const route = createLocalApiTaskListRoute({
async listTaskDefinitions(query) {
calls.push(query);
return {
definitions: [task('task-a', { enabled: false })],
truncated: true,
next: { taskId: 'task-a' },
};
},
});
const response = await route.handle({
projectId: 'prj_default',
input: { limit: 1 },
});
assert.deepEqual(calls, [{ projectId: 'prj_default', limit: 1 }]);
assert.deepEqual(response, {
statusCode: 200,
body: {
tasks: [
{
taskId: 'task-a',
revision: 2,
name: 'Task task-a',
kind: 'command',
specSchema: 'qinglong/command@v1',
enabled: false,
updatedAtMs: 20,
},
],
hasMore: true,
next: { taskId: 'task-a' },
},
});
assert.equal(JSON.stringify(response).includes('secret-adjacent'), false);
assert.equal(JSON.stringify(response).includes('private'), false);
});
test('fails closed on cross-Project, malformed and unavailable pages', async () => {
for (const page of [
{
definitions: [task('task-a', { projectId: 'prj_other' })],
truncated: false,
},
{
definitions: [task('task-a', { kind: 'invented' })],
truncated: false,
},
]) {
const route = createLocalApiTaskListRoute({
async listTaskDefinitions() { return page; },
});
assert.deepEqual(
await route.handle({ projectId: 'prj_default', input: {} }),
{ statusCode: 503, body: { code: 'task_list_unavailable' } },
);
}
const unavailable = createLocalApiTaskListRoute({
async listTaskDefinitions() { throw new Error('offline'); },
});
assert.deepEqual(
await unavailable.handle({ projectId: 'prj_default', input: {} }),
{ statusCode: 503, body: { code: 'task_list_unavailable' } },
);
assert.throws(() => createLocalApiTaskListRoute({}), TypeError);
});
@@ -0,0 +1,90 @@
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createTaskDefinitionRecord,
} = require('@qinglong/runtime-core/task-definition');
const {
createLocalApiTaskReadRoute,
} = require('../dist/task/taskReadRoute.js');
function task(overrides = {}) {
return createTaskDefinitionRecord(
{
projectId: 'prj_default',
taskId: 'task-a',
expectedRevision: null,
mutationId: '123e4567-e89b-42d3-a456-426614174101',
name: 'Task A',
description: 'secret-adjacent',
kind: 'command',
spec: {
schema: 'qinglong/command@v1',
config: { command: { kind: 'shell', command: 'private' } },
},
labels: { private: 'value' },
enabled: true,
occurredAtMs: 20,
...overrides,
},
10,
);
}
test('returns one shared current Task projection without definition internals', async () => {
const calls = [];
const definition = task({ enabled: false });
const route = createLocalApiTaskReadRoute({
async findCurrentTaskDefinition(projectId, taskId) {
calls.push([projectId, taskId]);
return definition;
},
});
const response = await route.handle({
projectId: 'prj_default',
taskId: 'task-a',
});
assert.deepEqual(calls, [['prj_default', 'task-a']]);
assert.deepEqual(response, {
statusCode: 200,
body: {
task: {
taskId: 'task-a',
revision: 1,
name: 'Task A',
kind: 'command',
specSchema: 'qinglong/command@v1',
enabled: false,
contentDigest: definition.contentDigest,
createdAtMs: 10,
updatedAtMs: 20,
},
},
});
const serialized = JSON.stringify(response);
assert.equal(serialized.includes('secret-adjacent'), false);
assert.equal(serialized.includes('private'), false);
});
test('masks absence and Project mismatch and fails closed on corruption', async () => {
for (const value of [null, task({ projectId: 'prj_other' })]) {
const route = createLocalApiTaskReadRoute({
async findCurrentTaskDefinition() { return value; },
});
assert.deepEqual(
await route.handle({ projectId: 'prj_default', taskId: 'task-a' }),
{ statusCode: 404, body: { code: 'task_not_found' } },
);
}
const corrupt = task();
const route = createLocalApiTaskReadRoute({
async findCurrentTaskDefinition() {
return { ...corrupt, contentDigest: '0'.repeat(64) };
},
});
assert.deepEqual(
await route.handle({ projectId: 'prj_default', taskId: 'task-a' }),
{ statusCode: 503, body: { code: 'task_query_unavailable' } },
);
assert.throws(() => createLocalApiTaskReadRoute({}), TypeError);
});
@@ -0,0 +1,118 @@
'use strict';
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
TASK_START_SCHEMA,
TaskStartFenceRejectedError,
TaskStartNotFoundError,
TaskStartUnavailableError,
} = require('@qinglong/runtime-core/task-start');
const {
createLocalApiTaskStartRoute,
} = require('../dist/task/taskStartRoute.js');
const IDS = [
'019f7300-0000-7000-8000-000000000701',
'019f7300-0000-7000-8000-000000000702',
'019f7300-0000-7000-8000-000000000703',
'019f7300-0000-7000-8000-000000000704',
];
const MUTATION_ID = '019f7300-0000-7000-8000-000000000700';
const DIGEST = 'a'.repeat(64);
const PRINCIPAL = Object.freeze({
subject: Object.freeze({ type: 'user', id: 'user-1' }),
authenticationId: 'credential-1',
authenticatedAtMs: 1,
expiresAtMs: 20,
assurance: 'single_factor',
});
function request(overrides = {}) {
return {
projectId: 'project-1',
taskId: 'task-1',
body: {
schema: TASK_START_SCHEMA,
mutationId: MUTATION_ID,
expectedRevision: 3,
expectedContentDigest: DIGEST,
},
principal: PRINCIPAL,
policyFence: { projectVersion: 2, bindingVersion: 4 },
...overrides,
};
}
function receipt(overrides = {}) {
return {
status: 'accepted',
projectId: 'project-1',
taskId: 'task-1',
taskRevision: 3,
taskContentDigest: DIGEST,
runId: IDS[0],
attemptId: IDS[1],
runStatus: 'queued',
runVersion: 2,
eventSequence: 2,
executorType: 'local_process',
executionRevisionDigest: 'b'.repeat(64),
createdAtMs: 10,
...overrides,
};
}
function route(repository) {
let index = 0;
return createLocalApiTaskStartRoute(repository, () => IDS[index++]);
}
test('publishes one server-owned Task start command and exact receipt', async () => {
let observed;
const result = await route({
async startTask(command) {
observed = command;
return receipt();
},
}).handle(request());
assert.deepEqual(result, {
statusCode: 202,
body: { schema: TASK_START_SCHEMA, ...receipt() },
});
assert.deepEqual(observed, {
projectId: 'project-1',
taskId: 'task-1',
mutationId: MUTATION_ID,
expectedRevision: 3,
expectedContentDigest: DIGEST,
runId: IDS[0],
attemptId: IDS[1],
createdEventId: IDS[2],
queuedEventId: IDS[3],
subject: PRINCIPAL.subject,
policyFence: { projectVersion: 2, bindingVersion: 4 },
});
});
test('rejects widened bodies and maps replay plus stable failures', async () => {
let calls = 0;
assert.equal((await route({
async startTask() { calls += 1; return receipt(); },
}).handle(request({ body: { ...request().body, command: '/bin/sh' } }))).statusCode, 400);
assert.equal(calls, 0);
assert.equal((await route({
async startTask() { return receipt({ status: 'existing' }); },
}).handle(request())).statusCode, 200);
for (const [error, statusCode, code] of [
[new TaskStartNotFoundError(), 404, 'task_not_found'],
[new TaskStartFenceRejectedError('task_disabled'), 409, 'task_start_fence_rejected'],
[new TaskStartUnavailableError(), 503, 'task_start_unavailable'],
]) {
const result = await route({ async startTask() { throw error; } }).handle(request());
assert.equal(result.statusCode, statusCode);
assert.equal(result.body.code, code);
}
});