Initial project documentation and status
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* Durable, single-instance project worker. It executes bounded checkpoints and
|
||||
* persists state. It never sends non-read-only MeshCentral operations.
|
||||
*/
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { spawn } = require('node:child_process');
|
||||
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const statePath = path.join(root, 'PROJECT-PROGRESS.json');
|
||||
const lockPath = path.join(root, '.worker.lock');
|
||||
const implementation = path.join(root, 'implementation');
|
||||
const maxLiveFailures = 3;
|
||||
const intervalMs = 15 * 60 * 1000;
|
||||
|
||||
let lock;
|
||||
try {
|
||||
lock = fs.openSync(lockPath, 'wx');
|
||||
fs.writeFileSync(lock, `${process.pid}\n`, 'utf8');
|
||||
} catch {
|
||||
process.stderr.write('worker already running or lock unavailable\n');
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
function readState() {
|
||||
return JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
||||
}
|
||||
function writeState(state) {
|
||||
state.worker = state.worker || {};
|
||||
state.worker.pid = process.pid;
|
||||
state.worker.lastActivityAt = new Date().toISOString();
|
||||
fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
||||
}
|
||||
function run(command, args) {
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn(command, args, { cwd: implementation, stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
let output = '';
|
||||
child.stdout.on('data', (chunk) => { output += chunk.toString(); });
|
||||
child.stderr.on('data', (chunk) => { output += chunk.toString(); });
|
||||
child.on('close', (code) => resolve({ code: code ?? 1, output: output.slice(-4000) }));
|
||||
child.on('error', () => resolve({ code: 1, output: '' }));
|
||||
});
|
||||
}
|
||||
async function checkpoint() {
|
||||
const state = readState();
|
||||
if (state.status === 'blocked' || state.status === 'completed') return false;
|
||||
state.status = 'working';
|
||||
state.currentTask = 'Run local verification, then perform the bounded read-only compatibility probe.';
|
||||
writeState(state);
|
||||
|
||||
for (const [label, command, args] of [
|
||||
['tests', 'npm', ['test', '--', '--runInBand']],
|
||||
['build', 'npm', ['run', 'build']],
|
||||
['lint', 'npm', ['run', 'lint']],
|
||||
]) {
|
||||
const result = await run(command, args);
|
||||
const latest = readState();
|
||||
latest.lastVerification = latest.lastVerification || {};
|
||||
latest.lastVerification[label] = result.code === 0 ? 'passed' : 'failed';
|
||||
latest.lastVerificationAt = new Date().toISOString();
|
||||
latest.nextAction = result.code === 0 ? 'Run the fixed-scope read-only live probe.' : `Fix local ${label} failure before live testing.`;
|
||||
writeState(latest);
|
||||
if (result.code !== 0) {
|
||||
latest.status = 'blocked';
|
||||
latest.blocker = `Local ${label} verification failed.`;
|
||||
writeState(latest);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const latest = readState();
|
||||
if ((latest.liveTestFailures || 0) >= maxLiveFailures) {
|
||||
latest.status = 'blocked';
|
||||
latest.blocker = 'Maximum consecutive live-test failures reached; live testing stopped.';
|
||||
latest.nextAction = 'Review sanitized protocol diagnostics manually; do not retry live testing.';
|
||||
writeState(latest);
|
||||
return false;
|
||||
}
|
||||
|
||||
const probe = await run('npm', ['run', 'live-test']);
|
||||
const afterProbe = readState();
|
||||
if (probe.code === 0) {
|
||||
afterProbe.status = 'completed';
|
||||
afterProbe.completedMilestones = afterProbe.completedMilestones || [];
|
||||
afterProbe.completedMilestones.push({ task: 'Fixed-scope read-only live compatibility probe passed', completedAt: new Date().toISOString() });
|
||||
afterProbe.blocker = null;
|
||||
afterProbe.nextAction = 'Document live compatibility result and prepare the read-only MVP.';
|
||||
} else {
|
||||
afterProbe.liveTestFailures = (afterProbe.liveTestFailures || 0) + 1;
|
||||
afterProbe.status = afterProbe.liveTestFailures >= maxLiveFailures ? 'blocked' : 'working';
|
||||
afterProbe.blocker = `Read-only live probe failed at the known safe stage; consecutive failures: ${afterProbe.liveTestFailures}.`;
|
||||
afterProbe.nextAction = afterProbe.liveTestFailures >= maxLiveFailures
|
||||
? 'Live testing stopped at the configured failure limit.'
|
||||
: 'Inspect the sanitized failure stage before any further live retry.';
|
||||
}
|
||||
writeState(afterProbe);
|
||||
return afterProbe.status === 'working';
|
||||
}
|
||||
|
||||
async function main() {
|
||||
process.on('SIGTERM', () => { try { fs.closeSync(lock); fs.unlinkSync(lockPath); } catch {} process.exit(0); });
|
||||
try {
|
||||
let keepGoing = await checkpoint();
|
||||
while (keepGoing) {
|
||||
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
||||
keepGoing = await checkpoint();
|
||||
}
|
||||
} finally {
|
||||
try { fs.closeSync(lock); fs.unlinkSync(lockPath); } catch {}
|
||||
}
|
||||
}
|
||||
main().catch((error) => {
|
||||
try {
|
||||
const state = readState();
|
||||
state.status = 'blocked';
|
||||
state.blocker = 'Worker orchestration failed before completing its checkpoint.';
|
||||
state.nextAction = 'Inspect worker error and restart only after confirming no worker is active.';
|
||||
writeState(state);
|
||||
} catch {}
|
||||
process.stderr.write(`${error instanceof Error ? error.message : 'worker failed'}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user