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,394 @@
require('ts-node/register/transpile-only');
const assert = require('node:assert/strict');
const { test } = require('node:test');
const { QueryTypes, Sequelize } = require('sequelize');
const {
defineSchemaMigrationModel,
} = require('../../back/data/schemaMigration');
const {
projectPolicyMigration,
} = require('../../back/migrations/0017-project-policy');
const {
approvalRequestMigration,
} = require('../../back/migrations/0020-approval-requests');
const {
approvedActionDispatchExecutionMigration,
} = require('../../back/migrations/0021-approved-action-dispatch-executions');
const {
approvedActionRecoveryMigration,
} = require('../../back/migrations/0022-approved-action-recovery');
const {
APPROVED_ACTION_RECOVERY_AUTHORIZATION_AUTH_INDEX,
APPROVED_ACTION_RECOVERY_AUTHORIZATION_MUTATION_INDEX,
APPROVED_ACTION_RECOVERY_AUTHORIZATION_PROJECT_INDEX,
APPROVED_ACTION_RECOVERY_AUTHORIZATION_TABLE,
approvedActionRecoveryAuthorizationMigration,
} = require('../../back/migrations/0024-approved-action-recovery-authorization');
const { runMigrations } = require('../../back/migrations/runner');
const {
LegacySequelizeApprovedActionDispatchRepository,
} = require('../../back/runtime/adapters/legacy-sequelize/approvedActionDispatchRepository');
const {
LegacySequelizeApprovedActionRecoveryRepository,
} = require('../../back/runtime/adapters/legacy-sequelize/approvedActionRecoveryRepository');
const {
LegacySequelizeApprovalRequestRepository,
} = require('../../back/runtime/adapters/legacy-sequelize/approvalRequestRepository');
const {
LegacySequelizeProjectPolicyRepository,
} = require('../../back/runtime/adapters/legacy-sequelize/projectPolicyRepository');
const {
ApprovedActionManualRecoveryService,
} = require('../../back/runtime/application/approvedActionManualRecoveryService');
const {
ApprovalRequestService,
} = require('../../back/runtime/application/approvalRequestService');
const {
ProjectPolicyEngine,
} = require('../../back/runtime/application/projectPolicyEngine');
const {
ApprovedActionRecoveryRepositoryError,
ApprovedActionRecoveryFenceRejectedError,
} = require('../../back/runtime/domain/approvedActionRecovery');
const {
ApprovedActionRecoveryAuthorizationDeniedError,
ApprovedActionRecoveryHumanRequiredError,
ApprovedActionRecoveryStrongAuthenticationRequiredError,
} = require('../../back/runtime/domain/approvedActionRecoveryAuthorization');
const PROJECT_ID = 'default';
const AGENT = Object.freeze({ type: 'agent', id: 'agent-1' });
const OWNER = Object.freeze({ type: 'user', id: 'owner-1' });
const SYSTEM = Object.freeze({ type: 'system', id: 'approval-dispatcher' });
const BASE_TIME = 300_000;
const RESOLUTION_TIME = BASE_TIME + 2_000;
async function migrate(database) {
await runMigrations({
database,
migrationModel: defineSchemaMigrationModel(database),
migrations: [
projectPolicyMigration,
approvalRequestMigration,
approvedActionDispatchExecutionMigration,
approvedActionRecoveryMigration,
approvedActionRecoveryAuthorizationMigration,
],
logger: { info() {} },
});
}
async function bind(repository, subject, role, mutationId) {
await repository.append({
expectedCurrentVersion: 0,
binding: {
projectId: PROJECT_ID,
subject,
version: 1,
state: 'active',
role,
mutationId,
changedBy: OWNER,
createdAtMs: BASE_TIME - 100,
},
});
}
async function setup(t) {
const database = new Sequelize({
dialect: 'sqlite',
storage: ':memory:',
logging: false,
});
t.after(() => database.close());
await migrate(database);
const policyRepository = new LegacySequelizeProjectPolicyRepository(database);
await bind(policyRepository, OWNER, 'owner', 'bind-owner');
await bind(policyRepository, AGENT, 'operator', 'bind-agent');
const policy = new ProjectPolicyEngine(policyRepository);
const approvals = new ApprovalRequestService(
new LegacySequelizeApprovalRequestRepository(database),
policy,
);
const executions = new LegacySequelizeApprovedActionDispatchRepository(
database,
);
const recovery = new LegacySequelizeApprovedActionRecoveryRepository(
database,
);
const action = {
permission: 'tool.call:filesystem.write',
actionType: 'tool_call',
actionRef: 'manual-recovery-plan',
actionDigest: 'a'.repeat(64),
previewDigest: 'f'.repeat(64),
};
await approvals.create({
id: 'approval-manual-recovery',
projectId: PROJECT_ID,
action,
risk: 'critical',
requestedBy: AGENT,
requestedAtMs: BASE_TIME,
expiresAtMs: BASE_TIME + 60_000,
});
await approvals.decide({
requestId: 'approval-manual-recovery',
expectedVersion: 1,
decisionId: 'decision-manual-recovery',
decision: 'approved',
reasonCode: 'reviewed_action',
decidedBy: OWNER,
decidedAtMs: BASE_TIME + 10,
});
const dispatch = (
await approvals.consume({
requestId: 'approval-manual-recovery',
expectedVersion: 2,
consumptionId: 'consumption-manual-recovery',
dispatchId: 'dispatch-manual-recovery',
action,
requestedBy: AGENT,
consumedBy: SYSTEM,
consumedAtMs: BASE_TIME + 20,
})
).dispatch;
const claimed = await executions.claim({
dispatchId: dispatch.id,
owner: 'dispatcher-1',
leaseToken: 'execution-lease-1',
nowMs: BASE_TIME + 100,
leaseDurationMs: 100,
});
const started = await executions.start({
dispatchId: dispatch.id,
approvalRequestId: dispatch.approvalRequestId,
actionDigest: dispatch.action.actionDigest,
owner: 'dispatcher-1',
leaseToken: 'execution-lease-1',
expectedVersion: claimed.snapshot.execution.version,
startedAtMs: BASE_TIME + 110,
});
return {
database,
policyRepository,
policy,
recovery,
dispatch,
started,
};
}
function principal(overrides = {}) {
return {
subject: OWNER,
authenticationId: 'mfa-session-1',
authenticatedAtMs: RESOLUTION_TIME - 1_000,
expiresAtMs: RESOLUTION_TIME + 60_000,
assurance: 'multi_factor',
...overrides,
};
}
function input(state, overrides = {}) {
return {
dispatchId: state.dispatch.id,
expectedExecutionVersion: state.started.execution.version,
expectedRecoveryVersion: 0,
mutationId: 'manual-resolution-1',
decision: 'abandon_unknown',
reasonCode: 'operator_abandoned_unknown',
principal: principal(),
...overrides,
};
}
test('0024 stores a strong-auth and Policy-fenced fact with manual resolution', async (t) => {
const state = await setup(t);
const service = new ApprovedActionManualRecoveryService(
state.recovery,
state.policy,
() => RESOLUTION_TIME,
);
const result = await service.resolve(input(state));
assert.equal(result.status, 'resolved');
assert.equal(result.snapshot.action.execution.status, 'blocked');
assert.equal(result.snapshot.resolution.resolvedBy.id, OWNER.id);
assert.deepEqual(await service.resolve(input(state)), result);
const [fact] = await state.database.query(
`SELECT * FROM "${APPROVED_ACTION_RECOVERY_AUTHORIZATION_TABLE}"`,
{ type: QueryTypes.SELECT },
);
assert.equal(fact.dispatch_id, state.dispatch.id);
assert.equal(fact.mutation_id, 'manual-resolution-1');
assert.equal(fact.resolved_by_id, OWNER.id);
assert.equal(fact.authentication_id, 'mfa-session-1');
assert.equal(fact.assurance, 'multi_factor');
assert.equal(fact.project_version, 1);
assert.equal(fact.binding_version, 1);
assert.match(fact.fact_digest, /^[0-9a-f]{64}$/);
const indexes = new Set(
(
await state.database
.getQueryInterface()
.showIndex(APPROVED_ACTION_RECOVERY_AUTHORIZATION_TABLE)
).map((index) => index.name),
);
for (const name of [
APPROVED_ACTION_RECOVERY_AUTHORIZATION_MUTATION_INDEX,
APPROVED_ACTION_RECOVERY_AUTHORIZATION_PROJECT_INDEX,
APPROVED_ACTION_RECOVERY_AUTHORIZATION_AUTH_INDEX,
]) {
assert.ok(indexes.has(name));
}
});
test('requires a stable User and recent strong authentication before storage reads', async () => {
let reads = 0;
const repository = {
async findById() {
reads += 1;
return null;
},
};
const policy = {
async decideWithFence() {
throw new Error('unreachable');
},
};
const service = new ApprovedActionManualRecoveryService(
repository,
policy,
() => RESOLUTION_TIME,
);
const base = {
dispatchId: 'dispatch-auth-check',
expectedExecutionVersion: 2,
expectedRecoveryVersion: 0,
mutationId: 'manual-auth-check',
decision: 'confirm_failed',
reasonCode: 'human_confirmed_failure',
};
await assert.rejects(
service.resolve({
...base,
principal: principal({
subject: { type: 'agent', id: 'agent-1' },
}),
}),
ApprovedActionRecoveryHumanRequiredError,
);
await assert.rejects(
service.resolve({
...base,
principal: principal({ assurance: 'single_factor' }),
}),
ApprovedActionRecoveryStrongAuthenticationRequiredError,
);
await assert.rejects(
service.resolve({
...base,
principal: principal({ authenticatedAtMs: RESOLUTION_TIME - 300_001 }),
}),
ApprovedActionRecoveryStrongAuthenticationRequiredError,
);
assert.equal(reads, 0);
});
test('operator Policy is denied and an authorization revocation race is fenced', async (t) => {
const denied = await setup(t);
await denied.policyRepository.append({
expectedCurrentVersion: 0,
binding: {
projectId: PROJECT_ID,
subject: { type: 'user', id: 'operator-1' },
version: 1,
state: 'active',
role: 'operator',
mutationId: 'bind-operator',
changedBy: OWNER,
createdAtMs: BASE_TIME,
},
});
const deniedService = new ApprovedActionManualRecoveryService(
denied.recovery,
denied.policy,
() => RESOLUTION_TIME,
);
await assert.rejects(
deniedService.resolve(
input(denied, {
principal: principal({
subject: { type: 'user', id: 'operator-1' },
}),
}),
),
ApprovedActionRecoveryAuthorizationDeniedError,
);
const raced = await setup(t);
const racingPolicy = {
async decideWithFence(request) {
const decision = await raced.policy.decideWithFence(request);
await raced.policyRepository.append({
expectedCurrentVersion: 1,
binding: {
projectId: PROJECT_ID,
subject: OWNER,
version: 2,
state: 'revoked',
mutationId: 'revoke-owner-during-recovery',
changedBy: OWNER,
createdAtMs: RESOLUTION_TIME,
},
});
return decision;
},
};
const racedService = new ApprovedActionManualRecoveryService(
raced.recovery,
racingPolicy,
() => RESOLUTION_TIME,
);
await assert.rejects(
racedService.resolve(input(raced)),
ApprovedActionRecoveryFenceRejectedError,
);
const after = await raced.recovery.findById(raced.dispatch.id);
assert.equal(after.action.execution.status, 'executing');
assert.equal(after.recovery.status, 'armed');
});
test('authorization fact failure rolls the manual terminal transition back', async (t) => {
const state = await setup(t);
await state.database.query(
`CREATE TRIGGER reject_recovery_authorization
BEFORE INSERT ON "${APPROVED_ACTION_RECOVERY_AUTHORIZATION_TABLE}"
BEGIN SELECT RAISE(ABORT, 'authorization rejected'); END`,
);
const service = new ApprovedActionManualRecoveryService(
state.recovery,
state.policy,
() => RESOLUTION_TIME,
);
await assert.rejects(
service.resolve(input(state)),
ApprovedActionRecoveryRepositoryError,
);
const after = await state.recovery.findById(state.dispatch.id);
assert.equal(after.action.execution.status, 'executing');
assert.equal(after.recovery.status, 'armed');
assert.equal(after.resolution, null);
assert.equal(
(
await state.database.query(
`SELECT dispatch_id FROM "${APPROVED_ACTION_RECOVERY_AUTHORIZATION_TABLE}"`,
{ type: QueryTypes.SELECT },
)
).length,
0,
);
});