import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' const root = resolve(new URL('..', import.meta.url).pathname) const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse) const [workflow, inventory, exactPlan] = await Promise.all([ load('config/freecad-gui-workflow-plan.json'), load('config/freecad-gui-command-inventory.json'), load('config/freecad-web-exact-parity-plan.json'), ]) const fail = (message) => { throw new Error(`FreeCAD GUI workflow plan: ${message}`) } const lockedCommit = '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' if (workflow.schemaVersion !== 1 || workflow.baseline?.freecadVersion !== '1.1.1' || workflow.baseline?.commit !== lockedCommit) fail('baseline mismatch.') if (inventory.baseline?.commit !== lockedCommit || exactPlan.baseline?.freecadCommit !== lockedCommit) fail('input baseline mismatch.') const family = workflow.family if (!family?.id || !family.title || !family.workbench || !family.primaryCommand || !family.primaryExactTask || !family.selectionReason) fail('selected family metadata is incomplete.') if (!Array.isArray(family.commandIds) || family.commandIds.length === 0 || !family.commandIds.includes(family.primaryCommand)) fail('selected command family is incomplete.') if (!Array.isArray(family.relatedExactTasks) || family.relatedExactTasks.length === 0) fail('related exact tasks are missing.') const exactTaskIds = new Set(exactPlan.programs.flatMap((program) => program.tasks.map((task) => task.id))) for (const taskId of [family.primaryExactTask, ...family.relatedExactTasks]) if (!exactTaskIds.has(taskId)) fail(`selected family references unknown exact task ${taskId}.`) const commandById = new Map(inventory.commands.map((command) => [command.id, command])) for (const commandId of family.commandIds) if (!commandById.has(commandId)) fail(`selected command ${commandId} is absent from the runtime inventory.`) const primary = commandById.get(family.primaryCommand) const sensitivity = workflow.sensitivityEvidence if (sensitivity?.source !== 'config/freecad-gui-command-inventory.json' || sensitivity.commandId !== family.primaryCommand) fail('sensitivity provenance is incomplete.') if (sensitivity.documentSensitive !== primary.documentSensitive || sensitivity.selectionSensitive !== primary.selectionSensitive) fail('selected sensitivity flags do not match the runtime inventory.') for (const caseName of ['noDocument', 'documentNoSelection', 'selectedPartBox']) { if (JSON.stringify(sensitivity.cases?.[caseName]) !== JSON.stringify(primary.cases?.[caseName]?.states)) fail(`${caseName} sensitivity evidence is stale.`) } const stateNames = ['success', 'disabled', 'failure', 'cancel', 'recovery'] for (const stateName of stateNames) { const recipe = workflow.stateRecipes?.[stateName] if (!recipe?.setup || !recipe.action || !recipe.expected) fail(`${stateName} workflow recipe is incomplete.`) } const expectedTaskIds = Array.from({ length: 7 }, (_, index) => `ORA-GUI-WF-${String(index).padStart(3, '0')}`) if (!Array.isArray(workflow.progress) || workflow.progress.map((entry) => entry.taskId).join(',') !== expectedTaskIds.join(',')) fail('workflow progress must contain ordered ORA-GUI-WF-000 through 006 tasks.') const statuses = workflow.progress.map((entry) => entry.status) const inProgressCount = statuses.filter((status) => status === 'in_progress').length if (inProgressCount > 1 || statuses.some((status) => !['completed', 'in_progress', 'pending'].includes(status))) fail('workflow progress has invalid statuses.') const inProgressIndex = statuses.indexOf('in_progress') if (inProgressCount === 0 && statuses.some((status) => status !== 'completed')) fail('workflow progress without an in-progress task must be fully completed.') if (inProgressCount === 1 && (statuses.slice(0, inProgressIndex).some((status) => status !== 'completed') || statuses.slice(inProgressIndex + 1).some((status) => status !== 'pending'))) fail('workflow progress is not a contiguous serial queue.') for (const [index, entry] of workflow.progress.entries()) { if (!Array.isArray(entry.evidence)) fail(`${entry.taskId} evidence must be an array.`) if (index < inProgressIndex && entry.evidence.length === 0) fail(`${entry.taskId} is completed without evidence.`) } console.log(JSON.stringify({ status: 'freecad-gui-workflow-plan-pass', family: family.id, primaryCommand: family.primaryCommand, primaryExactTask: family.primaryExactTask, completed: statuses.filter((status) => status === 'completed').length, inProgress: inProgressIndex >= 0 ? workflow.progress[inProgressIndex].taskId : null, pending: statuses.filter((status) => status === 'pending').length, }, null, 2))