mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-23 12:05:27 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,447 @@
|
||||
import { createHash } from 'crypto';
|
||||
import { DataTypes, Op } from 'sequelize';
|
||||
import {
|
||||
APPROVED_ACTION_RECOVERY_CONTROL_STATUSES,
|
||||
APPROVED_ACTION_RECOVERY_DECISIONS,
|
||||
APPROVED_ACTION_RECOVERY_FINDINGS,
|
||||
APPROVED_ACTION_RECOVERY_SOURCES,
|
||||
MAX_APPROVED_ACTION_RECOVERY_FINDINGS,
|
||||
MAX_APPROVED_ACTION_RECOVERY_VERSION,
|
||||
} from '../runtime/domain/approvedActionRecovery';
|
||||
import { PROJECT_TABLE } from './0017-project-policy';
|
||||
import { APPROVED_ACTION_DISPATCH_EXECUTION_TABLE } from './0021-approved-action-dispatch-executions';
|
||||
import type { Migration } from './types';
|
||||
|
||||
export const APPROVED_ACTION_RECOVERY_CONTROL_TABLE =
|
||||
'ApprovedActionRecoveryControls';
|
||||
export const APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE =
|
||||
'ApprovedActionRecoveryResolutions';
|
||||
export const APPROVED_ACTION_RECOVERY_DUE_INDEX =
|
||||
'approved_action_recovery_due_idx';
|
||||
export const APPROVED_ACTION_RECOVERY_PROJECT_INDEX =
|
||||
'approved_action_recovery_project_idx';
|
||||
export const APPROVED_ACTION_RECOVERY_LEASE_INDEX =
|
||||
'approved_action_recovery_lease_idx';
|
||||
export const APPROVED_ACTION_RECOVERY_RESOLUTION_MUTATION_INDEX =
|
||||
'approved_action_recovery_resolution_mutation_uidx';
|
||||
export const APPROVED_ACTION_RECOVERY_RESOLUTION_PROJECT_INDEX =
|
||||
'approved_action_recovery_resolution_project_idx';
|
||||
|
||||
const controlColumns = [
|
||||
'dispatch_id',
|
||||
'project_id',
|
||||
'execution_version',
|
||||
'status',
|
||||
'version',
|
||||
'next_scan_at_ms',
|
||||
'lease_owner',
|
||||
'lease_token',
|
||||
'lease_expires_at_ms',
|
||||
'finding_count',
|
||||
'last_finding_mutation_id',
|
||||
'last_finding',
|
||||
'last_result_code',
|
||||
'last_evidence_digest',
|
||||
'resolution_mutation_id',
|
||||
'created_at_ms',
|
||||
'updated_at_ms',
|
||||
];
|
||||
|
||||
const resolutionColumns = [
|
||||
'dispatch_id',
|
||||
'project_id',
|
||||
'execution_version',
|
||||
'mutation_id',
|
||||
'source',
|
||||
'decision',
|
||||
'evidence_digest',
|
||||
'reason_code',
|
||||
'resolved_by_type',
|
||||
'resolved_by_id',
|
||||
'resolved_at_ms',
|
||||
];
|
||||
|
||||
const constraints = [
|
||||
'approved_action_recovery_status_check',
|
||||
'approved_action_recovery_execution_version_check',
|
||||
'approved_action_recovery_version_check',
|
||||
'approved_action_recovery_finding_count_check',
|
||||
'approved_action_recovery_lease_tuple_check',
|
||||
'approved_action_recovery_finding_tuple_check',
|
||||
'approved_action_recovery_finding_value_check',
|
||||
'approved_action_recovery_evidence_digest_check',
|
||||
'approved_action_recovery_resolution_mutation_tuple_check',
|
||||
'approved_action_recovery_timestamps_check',
|
||||
'approved_action_recovery_resolution_execution_version_check',
|
||||
'approved_action_recovery_resolution_source_check',
|
||||
'approved_action_recovery_resolution_decision_check',
|
||||
'approved_action_recovery_resolution_actor_tuple_check',
|
||||
'approved_action_recovery_resolution_source_tuple_check',
|
||||
'approved_action_recovery_resolution_evidence_digest_check',
|
||||
'approved_action_recovery_resolution_time_check',
|
||||
];
|
||||
|
||||
const manifest = {
|
||||
tables: {
|
||||
ApprovedActionRecoveryControls: controlColumns,
|
||||
ApprovedActionRecoveryResolutions: resolutionColumns,
|
||||
},
|
||||
indexes: [
|
||||
`${APPROVED_ACTION_RECOVERY_DUE_INDEX}(status,next_scan_at_ms,dispatch_id)`,
|
||||
`${APPROVED_ACTION_RECOVERY_PROJECT_INDEX}(project_id,status,created_at_ms,dispatch_id)`,
|
||||
`${APPROVED_ACTION_RECOVERY_LEASE_INDEX}(status,lease_expires_at_ms,dispatch_id)`,
|
||||
`${APPROVED_ACTION_RECOVERY_RESOLUTION_MUTATION_INDEX}(mutation_id) UNIQUE`,
|
||||
`${APPROVED_ACTION_RECOVERY_RESOLUTION_PROJECT_INDEX}(project_id,resolved_at_ms,dispatch_id)`,
|
||||
],
|
||||
constraints,
|
||||
};
|
||||
|
||||
export const approvedActionRecoveryManifest = manifest;
|
||||
|
||||
export const approvedActionRecoveryMigration: Migration = {
|
||||
id: '0022-approved-action-recovery',
|
||||
checksum: createHash('sha256').update(JSON.stringify(manifest)).digest('hex'),
|
||||
async up({ queryInterface, transaction }) {
|
||||
await queryInterface.createTable(
|
||||
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
||||
{
|
||||
dispatch_id: {
|
||||
type: DataTypes.STRING(64),
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
model: APPROVED_ACTION_DISPATCH_EXECUTION_TABLE,
|
||||
key: 'dispatch_id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
project_id: {
|
||||
type: DataTypes.STRING(128),
|
||||
allowNull: false,
|
||||
references: { model: PROJECT_TABLE, key: 'id' },
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
execution_version: { type: DataTypes.INTEGER, allowNull: false },
|
||||
status: { type: DataTypes.STRING(24), allowNull: false },
|
||||
version: { type: DataTypes.INTEGER, allowNull: false },
|
||||
next_scan_at_ms: { type: DataTypes.BIGINT, allowNull: true },
|
||||
lease_owner: { type: DataTypes.STRING(128), allowNull: true },
|
||||
lease_token: { type: DataTypes.STRING(128), allowNull: true },
|
||||
lease_expires_at_ms: { type: DataTypes.BIGINT, allowNull: true },
|
||||
finding_count: { type: DataTypes.INTEGER, allowNull: false },
|
||||
last_finding_mutation_id: {
|
||||
type: DataTypes.STRING(64),
|
||||
allowNull: true,
|
||||
},
|
||||
last_finding: { type: DataTypes.STRING(24), allowNull: true },
|
||||
last_result_code: { type: DataTypes.STRING(64), allowNull: true },
|
||||
last_evidence_digest: { type: DataTypes.STRING(64), allowNull: true },
|
||||
resolution_mutation_id: {
|
||||
type: DataTypes.STRING(64),
|
||||
allowNull: true,
|
||||
},
|
||||
created_at_ms: { type: DataTypes.BIGINT, allowNull: false },
|
||||
updated_at_ms: { type: DataTypes.BIGINT, allowNull: false },
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
await queryInterface.createTable(
|
||||
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
||||
{
|
||||
dispatch_id: {
|
||||
type: DataTypes.STRING(64),
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
model: APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
||||
key: 'dispatch_id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
project_id: {
|
||||
type: DataTypes.STRING(128),
|
||||
allowNull: false,
|
||||
references: { model: PROJECT_TABLE, key: 'id' },
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
execution_version: { type: DataTypes.INTEGER, allowNull: false },
|
||||
mutation_id: { type: DataTypes.STRING(64), allowNull: false },
|
||||
source: { type: DataTypes.STRING(24), allowNull: false },
|
||||
decision: { type: DataTypes.STRING(24), allowNull: false },
|
||||
evidence_digest: { type: DataTypes.STRING(64), allowNull: true },
|
||||
reason_code: { type: DataTypes.STRING(64), allowNull: false },
|
||||
resolved_by_type: { type: DataTypes.STRING(32), allowNull: true },
|
||||
resolved_by_id: { type: DataTypes.STRING(255), allowNull: true },
|
||||
resolved_at_ms: { type: DataTypes.BIGINT, allowNull: false },
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
for (const [table, field, minimum, maximum, name] of [
|
||||
[
|
||||
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
||||
'execution_version',
|
||||
1,
|
||||
MAX_APPROVED_ACTION_RECOVERY_VERSION,
|
||||
'approved_action_recovery_execution_version_check',
|
||||
],
|
||||
[
|
||||
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
||||
'version',
|
||||
0,
|
||||
MAX_APPROVED_ACTION_RECOVERY_VERSION,
|
||||
'approved_action_recovery_version_check',
|
||||
],
|
||||
[
|
||||
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
||||
'finding_count',
|
||||
0,
|
||||
MAX_APPROVED_ACTION_RECOVERY_FINDINGS,
|
||||
'approved_action_recovery_finding_count_check',
|
||||
],
|
||||
[
|
||||
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
||||
'execution_version',
|
||||
1,
|
||||
MAX_APPROVED_ACTION_RECOVERY_VERSION,
|
||||
'approved_action_recovery_resolution_execution_version_check',
|
||||
],
|
||||
] as const) {
|
||||
await queryInterface.addConstraint(table, {
|
||||
fields: [field],
|
||||
type: 'check',
|
||||
where: { [field]: { [Op.between]: [minimum, maximum] } },
|
||||
name,
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
for (const [table, field, values, name] of [
|
||||
[
|
||||
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
||||
'status',
|
||||
APPROVED_ACTION_RECOVERY_CONTROL_STATUSES,
|
||||
'approved_action_recovery_status_check',
|
||||
],
|
||||
[
|
||||
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
||||
'last_finding',
|
||||
APPROVED_ACTION_RECOVERY_FINDINGS,
|
||||
'approved_action_recovery_finding_value_check',
|
||||
],
|
||||
[
|
||||
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
||||
'source',
|
||||
APPROVED_ACTION_RECOVERY_SOURCES,
|
||||
'approved_action_recovery_resolution_source_check',
|
||||
],
|
||||
[
|
||||
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
||||
'decision',
|
||||
APPROVED_ACTION_RECOVERY_DECISIONS,
|
||||
'approved_action_recovery_resolution_decision_check',
|
||||
],
|
||||
] as const) {
|
||||
await queryInterface.addConstraint(table, {
|
||||
fields: [field],
|
||||
type: 'check',
|
||||
where: { [field]: { [Op.in]: values } },
|
||||
name,
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
await queryInterface.addConstraint(APPROVED_ACTION_RECOVERY_CONTROL_TABLE, {
|
||||
fields: ['lease_owner', 'lease_token', 'lease_expires_at_ms'],
|
||||
type: 'check',
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ lease_owner: null, lease_token: null, lease_expires_at_ms: null },
|
||||
{
|
||||
lease_owner: { [Op.not]: null },
|
||||
lease_token: { [Op.not]: null },
|
||||
lease_expires_at_ms: { [Op.not]: null },
|
||||
},
|
||||
],
|
||||
},
|
||||
name: 'approved_action_recovery_lease_tuple_check',
|
||||
transaction,
|
||||
});
|
||||
await queryInterface.addConstraint(APPROVED_ACTION_RECOVERY_CONTROL_TABLE, {
|
||||
fields: ['last_finding_mutation_id', 'last_finding', 'last_result_code'],
|
||||
type: 'check',
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{
|
||||
last_finding_mutation_id: null,
|
||||
last_finding: null,
|
||||
last_result_code: null,
|
||||
},
|
||||
{
|
||||
last_finding_mutation_id: { [Op.not]: null },
|
||||
last_finding: { [Op.not]: null },
|
||||
last_result_code: { [Op.not]: null },
|
||||
},
|
||||
],
|
||||
},
|
||||
name: 'approved_action_recovery_finding_tuple_check',
|
||||
transaction,
|
||||
});
|
||||
for (const [table, field, name] of [
|
||||
[
|
||||
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
||||
'last_evidence_digest',
|
||||
'approved_action_recovery_evidence_digest_check',
|
||||
],
|
||||
[
|
||||
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
||||
'evidence_digest',
|
||||
'approved_action_recovery_resolution_evidence_digest_check',
|
||||
],
|
||||
] as const) {
|
||||
await queryInterface.addConstraint(table, {
|
||||
fields: [field],
|
||||
type: 'check',
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ [field]: null },
|
||||
queryInterface.sequelize.where(
|
||||
queryInterface.sequelize.fn(
|
||||
'length',
|
||||
queryInterface.sequelize.col(field),
|
||||
),
|
||||
64,
|
||||
),
|
||||
],
|
||||
},
|
||||
name,
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
await queryInterface.addConstraint(APPROVED_ACTION_RECOVERY_CONTROL_TABLE, {
|
||||
fields: ['status', 'resolution_mutation_id'],
|
||||
type: 'check',
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ status: 'resolved', resolution_mutation_id: { [Op.not]: null } },
|
||||
{
|
||||
status: { [Op.not]: 'resolved' },
|
||||
resolution_mutation_id: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
name: 'approved_action_recovery_resolution_mutation_tuple_check',
|
||||
transaction,
|
||||
});
|
||||
await queryInterface.addConstraint(APPROVED_ACTION_RECOVERY_CONTROL_TABLE, {
|
||||
fields: ['created_at_ms', 'updated_at_ms'],
|
||||
type: 'check',
|
||||
where: {
|
||||
created_at_ms: { [Op.gte]: 0 },
|
||||
updated_at_ms: { [Op.gte]: { [Op.col]: 'created_at_ms' } },
|
||||
},
|
||||
name: 'approved_action_recovery_timestamps_check',
|
||||
transaction,
|
||||
});
|
||||
await queryInterface.addConstraint(
|
||||
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
||||
{
|
||||
fields: ['resolved_by_type', 'resolved_by_id'],
|
||||
type: 'check',
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ resolved_by_type: null, resolved_by_id: null },
|
||||
{
|
||||
resolved_by_type: 'user',
|
||||
resolved_by_id: { [Op.not]: null },
|
||||
},
|
||||
],
|
||||
},
|
||||
name: 'approved_action_recovery_resolution_actor_tuple_check',
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
await queryInterface.addConstraint(
|
||||
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
||||
{
|
||||
fields: ['source', 'decision', 'evidence_digest', 'resolved_by_type'],
|
||||
type: 'check',
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{
|
||||
source: 'automatic_evidence',
|
||||
decision: {
|
||||
[Op.in]: ['confirm_succeeded', 'confirm_failed'],
|
||||
},
|
||||
evidence_digest: { [Op.not]: null },
|
||||
resolved_by_type: null,
|
||||
},
|
||||
{ source: 'human', resolved_by_type: 'user' },
|
||||
],
|
||||
},
|
||||
name: 'approved_action_recovery_resolution_source_tuple_check',
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
await queryInterface.addConstraint(
|
||||
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
||||
{
|
||||
fields: ['resolved_at_ms'],
|
||||
type: 'check',
|
||||
where: { resolved_at_ms: { [Op.gte]: 0 } },
|
||||
name: 'approved_action_recovery_resolution_time_check',
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
await queryInterface.addIndex(
|
||||
APPROVED_ACTION_RECOVERY_CONTROL_TABLE,
|
||||
['status', 'next_scan_at_ms', 'dispatch_id'],
|
||||
{ name: APPROVED_ACTION_RECOVERY_DUE_INDEX, transaction },
|
||||
);
|
||||
await queryInterface.addIndex(APPROVED_ACTION_RECOVERY_CONTROL_TABLE, {
|
||||
fields: ['project_id', 'status', 'created_at_ms', 'dispatch_id'],
|
||||
name: APPROVED_ACTION_RECOVERY_PROJECT_INDEX,
|
||||
transaction,
|
||||
});
|
||||
await queryInterface.addIndex(APPROVED_ACTION_RECOVERY_CONTROL_TABLE, {
|
||||
fields: ['status', 'lease_expires_at_ms', 'dispatch_id'],
|
||||
name: APPROVED_ACTION_RECOVERY_LEASE_INDEX,
|
||||
transaction,
|
||||
});
|
||||
await queryInterface.addIndex(
|
||||
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
||||
['mutation_id'],
|
||||
{
|
||||
name: APPROVED_ACTION_RECOVERY_RESOLUTION_MUTATION_INDEX,
|
||||
unique: true,
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
await queryInterface.addIndex(
|
||||
APPROVED_ACTION_RECOVERY_RESOLUTION_TABLE,
|
||||
['project_id', 'resolved_at_ms', 'dispatch_id'],
|
||||
{
|
||||
name: APPROVED_ACTION_RECOVERY_RESOLUTION_PROJECT_INDEX,
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
await queryInterface.sequelize.query(
|
||||
`INSERT INTO "${APPROVED_ACTION_RECOVERY_CONTROL_TABLE}"
|
||||
(dispatch_id, project_id, execution_version, status, version,
|
||||
next_scan_at_ms, lease_owner, lease_token, lease_expires_at_ms,
|
||||
finding_count, last_finding_mutation_id, last_finding,
|
||||
last_result_code, last_evidence_digest,
|
||||
resolution_mutation_id, created_at_ms, updated_at_ms)
|
||||
SELECT dispatch_id, project_id, version, 'armed', 0,
|
||||
lease_expires_at_ms, NULL, NULL, NULL,
|
||||
0, NULL, NULL, NULL, NULL, NULL, started_at_ms, started_at_ms
|
||||
FROM "${APPROVED_ACTION_DISPATCH_EXECUTION_TABLE}"
|
||||
WHERE status = 'executing'`,
|
||||
{ transaction },
|
||||
);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user