fix: harden release gate and page delivery
Memind CI / Test, build, and release guards (push) Failing after 12m3s
Memind CI / Test, build, and release guards (push) Failing after 12m3s
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
export const SCENARIO_GROUP_COUNTS = Object.freeze({
|
||||
REL: 11,
|
||||
AUTH: 8,
|
||||
CHAT: 15,
|
||||
MEM: 16,
|
||||
FILE: 8,
|
||||
IMGPG: 8,
|
||||
XLS: 12,
|
||||
PAGE: 13,
|
||||
DATA: 13,
|
||||
SEARCH: 8,
|
||||
AGENT: 11,
|
||||
SCHED: 5,
|
||||
WX: 13,
|
||||
BILL: 7,
|
||||
MS: 9,
|
||||
PLAZA: 5,
|
||||
CFG: 8,
|
||||
UI: 8,
|
||||
COMP: 9,
|
||||
});
|
||||
|
||||
export const SCENARIO_MODE_BY_GROUP = Object.freeze({
|
||||
REL: 'upgrade',
|
||||
AUTH: 'scenarios',
|
||||
CHAT: 'scenarios',
|
||||
MEM: 'scenarios',
|
||||
FILE: 'scenarios',
|
||||
IMGPG: 'scenarios',
|
||||
XLS: 'scenarios',
|
||||
PAGE: 'scenarios',
|
||||
DATA: 'scenarios',
|
||||
SEARCH: 'providers',
|
||||
AGENT: 'scenarios',
|
||||
SCHED: 'scenarios',
|
||||
WX: 'scenarios',
|
||||
BILL: 'scenarios',
|
||||
MS: 'scenarios',
|
||||
PLAZA: 'scenarios',
|
||||
CFG: 'deterministic',
|
||||
UI: 'browser',
|
||||
COMP: 'upgrade',
|
||||
});
|
||||
|
||||
const ALWAYS_REQUIRED = new Set([
|
||||
...range('REL', 1, 11),
|
||||
'AUTH-01',
|
||||
'AUTH-04',
|
||||
'AUTH-05',
|
||||
'AUTH-06',
|
||||
'CHAT-01',
|
||||
'CHAT-02',
|
||||
'CHAT-06',
|
||||
'CHAT-07',
|
||||
'CHAT-08',
|
||||
'CHAT-09',
|
||||
'CHAT-11',
|
||||
'CHAT-12',
|
||||
'CHAT-13',
|
||||
'CHAT-15',
|
||||
'MEM-08',
|
||||
'MEM-13',
|
||||
'MEM-14',
|
||||
'MEM-15',
|
||||
'MEM-16',
|
||||
'FILE-07',
|
||||
'PAGE-08',
|
||||
'PAGE-09',
|
||||
'PAGE-10',
|
||||
'PAGE-12',
|
||||
'DATA-06',
|
||||
'DATA-10',
|
||||
'DATA-12',
|
||||
'AGENT-10',
|
||||
'AGENT-11',
|
||||
...range('WX', 9, 13),
|
||||
'BILL-02',
|
||||
'BILL-03',
|
||||
'BILL-04',
|
||||
'MS-01',
|
||||
'MS-03',
|
||||
'MS-04',
|
||||
'CFG-08',
|
||||
...range('COMP', 1, 4),
|
||||
'COMP-08',
|
||||
'COMP-09',
|
||||
]);
|
||||
|
||||
function range(prefix, start, end) {
|
||||
return Array.from(
|
||||
{ length: end - start + 1 },
|
||||
(_, index) => `${prefix}-${String(start + index).padStart(2, '0')}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function expectedScenarioIds() {
|
||||
return Object.entries(SCENARIO_GROUP_COUNTS).flatMap(
|
||||
([prefix, count]) => range(prefix, 1, count),
|
||||
);
|
||||
}
|
||||
|
||||
export function isScenarioExemptable(id) {
|
||||
return !ALWAYS_REQUIRED.has(id);
|
||||
}
|
||||
|
||||
export function validateScenarioCatalog(catalog) {
|
||||
const expected = expectedScenarioIds();
|
||||
const expectedSet = new Set(expected);
|
||||
const ids = catalog.map((scenario) => scenario.id);
|
||||
const unique = new Set(ids);
|
||||
const missing = expected.filter((id) => !unique.has(id));
|
||||
const unexpected = ids.filter((id) => !expectedSet.has(id));
|
||||
const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
|
||||
|
||||
if (catalog.length !== expected.length || unique.size !== expected.length) {
|
||||
throw new Error(
|
||||
`Release gate catalog must contain exactly ${expected.length} unique scenarios `
|
||||
+ `(found ${catalog.length}/${unique.size})`,
|
||||
);
|
||||
}
|
||||
if (missing.length || unexpected.length || duplicates.length) {
|
||||
throw new Error(
|
||||
`Release gate catalog mismatch: missing=${missing.join(',') || '-'} `
|
||||
+ `unexpected=${unexpected.join(',') || '-'} duplicates=${[...new Set(duplicates)].join(',') || '-'}`,
|
||||
);
|
||||
}
|
||||
for (const scenario of catalog) {
|
||||
if (!scenario.name || !scenario.group || !scenario.mode) {
|
||||
throw new Error(`Scenario ${scenario.id} is missing name, group, or mode`);
|
||||
}
|
||||
}
|
||||
return catalog;
|
||||
}
|
||||
|
||||
export async function loadScenarioCatalog({
|
||||
root = path.resolve(new URL('..', import.meta.url).pathname),
|
||||
guardianPath = path.join(root, 'docs', 'production-release-guardian.md'),
|
||||
} = {}) {
|
||||
const source = await fs.readFile(guardianPath, 'utf8');
|
||||
const catalog = [];
|
||||
const scenarioPattern = /^- \*\*([A-Z]+-\d{2})\*\*:(.+)$/gm;
|
||||
for (const match of source.matchAll(scenarioPattern)) {
|
||||
const id = match[1];
|
||||
const group = id.split('-')[0];
|
||||
catalog.push({
|
||||
id,
|
||||
name: match[2].trim(),
|
||||
group,
|
||||
mode: SCENARIO_MODE_BY_GROUP[group],
|
||||
exemptable: isScenarioExemptable(id),
|
||||
});
|
||||
}
|
||||
return validateScenarioCatalog(catalog);
|
||||
}
|
||||
Reference in New Issue
Block a user