grisp-shadow-fleet / agents /orchestrate.mjs
SNAPKITTYWEST's picture
push from SNAPKITTYWEST/grisp-shadow-fleet
b8a03fa verified
Raw
History Blame Contribute Delete
11.3 kB
#!/usr/bin/env node
import { readdirSync, statSync, mkdirSync, readFileSync } from 'node:fs'
import { createServer } from 'node:net'
import { createHash } from 'node:crypto'
import { join, relative, dirname, basename, extname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'
import { verifyOnce } from './icp-verifier.mjs'
import { run as runMetrics } from './metric-stream.mjs'
import { run as runBifrost } from './bifrost-translator.mjs'
import { run as runWatermark } from './watermark.mjs'
import { sealEvent } from './worm-chain.mjs'
const __dir = dirname(fileURLToPath(import.meta.url))
const ROOT = join(__dir, '..')
const PAGES = join(ROOT, 'pages')
const OUT = join(ROOT, 'bifrost-out')
const PUBLIC = join(ROOT, 'public')
const GOVERNANCE = join(ROOT, 'governance', 'shadow-orchestrator.pl')
const DAY_MS = 24 * 60 * 60 * 1000
const require = createRequire(import.meta.url)
const pl = require('tau-prolog')
let tick = 0
let running = false
const relaySockets = new Set()
function parseArgs() {
const args = process.argv.slice(2)
const intervalIdx = args.indexOf('--interval')
return {
once: args.includes('--once'),
interval: intervalIdx >= 0 ? parseInt(args[intervalIdx + 1], 10) : parseInt(process.env.ORCHESTRATE_INTERVAL_MS || '30000', 10),
}
}
function walk(dir, match, files = []) {
const stack = [dir]
while (stack.length) {
const current = stack.pop()
let entries = []
try { entries = readdirSync(current) } catch { continue }
for (const entry of entries) {
const full = join(current, entry)
let stat
try { stat = statSync(full) } catch { continue }
if (stat.isDirectory()) stack.push(full)
else if (match(full, stat)) files.push(full)
}
}
return files
}
function recentPageSources() {
const cutoff = Date.now() - DAY_MS
return walk(PAGES, (file, stat) => extname(file) === '.ts' && stat.mtimeMs >= cutoff)
}
function outFiles() {
return new Set(walk(OUT, file => true).map(file => relative(OUT, file)))
}
function prefixForSource(source) {
const rel = relative(PAGES, source)
const parts = rel.split(/[\\/]/).filter(Boolean)
const stem = basename(source).replace(/\.[^.]+$/, '')
const scope = parts.length > 1 ? parts.slice(0, -1).join('-') : 'page'
return stem === 'index' ? `${scope}-` : `${scope}-`
}
async function quiet(fn) {
const out = console.log
const err = console.error
console.log = (...args) => err(...args)
console.error = (...args) => err(...args)
try { return await fn() }
finally {
console.log = out
console.error = err
}
}
function prologAtom(value) {
return `'${String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
}
function queryGovernance(goal) {
const program = readFileSync(GOVERNANCE, 'utf8')
return new Promise((resolve, reject) => {
const session = pl.create(1000)
session.consult(program, {
success: () => {
session.query(goal, {
success: () => {
session.answer(answer => resolve(answer !== false))
},
error: reject,
})
},
error: reject,
})
})
}
async function agentAllowed(name, phase) {
return queryGovernance(`agent_allowed(${prologAtom(name)}, ${prologAtom(phase)}).`)
}
async function statusAllowed(name, status) {
return queryGovernance(`status_verdict(${prologAtom(name)}, ${prologAtom(status)}, 'ALLOW').`)
}
async function dispatchGate(type) {
const allowed = await queryGovernance(`dispatch_verdict(${prologAtom(type)}, 'resurrect', 'ACCEPTED').`)
return { allowed, agent: 'resurrect', status: allowed ? 'ACCEPTED' : 'REJECTED' }
}
async function sealed(name, status, payload = {}) {
const allowed = await statusAllowed(name, status)
const finalStatus = allowed ? status : 'REJECTED'
const governance = allowed ? 'tau-prolog:allow' : 'tau-prolog:reject'
const { seal } = sealEvent(name, 'agent-result', { status: finalStatus, governance, ...payload })
return { name, status: finalStatus, seal, governance }
}
async function requireAgent(name, phase) {
if (await agentAllowed(name, phase)) return
throw new Error(`tau_prolog_denied:${phase}:${name}`)
}
function writeJsonLine(message) {
const line = JSON.stringify(message) + '\n'
process.stdout.write(line)
for (const socket of relaySockets) {
if (!socket.destroyed) socket.write(encodeWsFrame(JSON.stringify(message)))
}
}
async function runTick() {
if (running) return
running = true
tick += 1
const ts = new Date().toISOString()
const agents = []
try {
mkdirSync(PUBLIC, { recursive: true })
mkdirSync(OUT, { recursive: true })
await requireAgent('icp-verifier', 'tick')
const icp = await quiet(() => verifyOnce({}))
if (icp.status === 'DRIFT_DETECTED') {
const incident = sealEvent('ORCHESTRATOR', 'icp-drift-incident', { tick, ts, icp })
agents.push(await sealed('icp-verifier', 'DRIFT_DETECTED', { internalSeal: incident.seal }))
writeJsonLine({ tick, ts, agents })
return
}
agents.push(await sealed('icp-verifier', icp.status || 'VERIFIED', { internalSeal: icp.seal }))
await requireAgent('metric-stream', 'tick')
const metrics = await quiet(() => runMetrics({
repoPath: ROOT,
outPath: join(PUBLIC, 'metrics.json'),
}))
agents.push(await sealed('metric-stream', 'UPDATED', { internalSeal: metrics.wormSeal }))
await requireAgent('bifrost-translator', 'tick')
const before = outFiles()
const sources = recentPageSources()
const translations = []
for (const sourcePath of sources) {
const result = await quiet(() => runBifrost({
sourcePath,
targets: ['rust', 'lean4'],
outDir: OUT,
namePrefix: prefixForSource(sourcePath),
}))
translations.push(result)
}
agents.push(await sealed('bifrost-translator', sources.length ? 'TRANSLATED' : 'IDLE', {
files: sources.length,
internalSeals: translations.map(r => r.seal).filter(Boolean),
}))
await requireAgent('watermark', 'tick')
const after = outFiles()
const newFiles = [...after].filter(file => !before.has(file))
const watermark = await quiet(() => runWatermark({ target: OUT }))
agents.push(await sealed('watermark', watermark.marks.length ? 'WATERMARKED' : 'IDLE', {
files: newFiles,
filesMarked: watermark.marks.length,
internalSeal: watermark.seal,
}))
writeJsonLine({ tick, ts, agents })
} catch (error) {
const incident = sealEvent('ORCHESTRATOR', 'tick-error', { tick, ts, message: error.message })
agents.push(await sealed('orchestrator', 'ERROR', { internalSeal: incident.seal, message: error.message }))
writeJsonLine({ tick, ts, agents })
} finally {
running = false
}
}
function encodeWsFrame(text) {
const body = Buffer.from(text)
if (body.length < 126) return Buffer.concat([Buffer.from([0x81, body.length]), body])
if (body.length <= 0xffff) {
const header = Buffer.alloc(4)
header[0] = 0x81
header[1] = 126
header.writeUInt16BE(body.length, 2)
return Buffer.concat([header, body])
}
const header = Buffer.alloc(10)
header[0] = 0x81
header[1] = 127
header.writeBigUInt64BE(BigInt(body.length), 2)
return Buffer.concat([header, body])
}
function decodeWsFrame(buffer) {
if (buffer.length < 2) return null
const opcode = buffer[0] & 0x0f
if (opcode === 0x8) return { close: true }
const masked = (buffer[1] & 0x80) !== 0
let len = buffer[1] & 0x7f
let offset = 2
if (len === 126) {
if (buffer.length < 4) return null
len = buffer.readUInt16BE(2)
offset = 4
} else if (len === 127) {
if (buffer.length < 10) return null
len = Number(buffer.readBigUInt64BE(2))
offset = 10
}
const maskOffset = offset
if (masked) offset += 4
if (buffer.length < offset + len) return null
const payload = Buffer.from(buffer.slice(offset, offset + len))
if (masked) {
const mask = buffer.slice(maskOffset, maskOffset + 4)
for (let i = 0; i < payload.length; i += 1) payload[i] ^= mask[i % 4]
}
return { text: payload.toString('utf8') }
}
function acceptHandshake(socket, request) {
const key = request.match(/Sec-WebSocket-Key:\s*(.+)\r?\n/i)?.[1]?.trim()
if (!key) {
socket.destroy()
return false
}
const accept = createHash('sha1')
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
.digest('base64')
socket.write(
'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
`Sec-WebSocket-Accept: ${accept}\r\n\r\n`
)
relaySockets.add(socket)
socket.once('close', () => relaySockets.delete(socket))
socket.once('error', () => relaySockets.delete(socket))
return true
}
async function handleRelayMessage(text) {
let message
try { message = JSON.parse(text) } catch { return }
if (typeof message.type !== 'string' || !message.type.endsWith(':dispatch')) return
tick += 1
const ts = new Date().toISOString()
const gate = await dispatchGate(message.type)
if (!gate.allowed) {
const rejected = await sealed(gate.agent, gate.status, {
repo: message.repo,
dryRun: Boolean(message.dryRun),
reason: 'tau_prolog_dispatch_denied',
})
writeJsonLine({
tick,
ts,
type: 'ransom_worm:rejected',
repo: message.repo,
dryRun: Boolean(message.dryRun),
agents: [rejected],
})
return
}
const accepted = await sealed(gate.agent, gate.status, {
repo: message.repo,
dryRun: Boolean(message.dryRun),
})
writeJsonLine({
tick,
ts,
type: 'ransom_worm:accepted',
repo: message.repo,
dryRun: Boolean(message.dryRun),
agents: [accepted],
})
}
function startRelay(port) {
const server = createServer(socket => {
let handshaken = false
let pending = Buffer.alloc(0)
socket.on('data', chunk => {
if (!handshaken) {
const request = chunk.toString('utf8')
if (!request.includes('\r\n\r\n')) return
handshaken = acceptHandshake(socket, request)
return
}
pending = Buffer.concat([pending, chunk])
const frame = decodeWsFrame(pending)
if (!frame) return
pending = Buffer.alloc(0)
if (frame.close) {
socket.end()
return
}
handleRelayMessage(frame.text).catch(error => {
const incident = sealEvent('ORCHESTRATOR', 'relay-error', { message: error.message })
writeJsonLine({
tick,
ts: new Date().toISOString(),
type: 'relay:error',
message: error.message,
seal: incident.seal,
})
})
})
})
server.listen(port, '127.0.0.1', () => {
const address = server.address()
const boundPort = typeof address === 'object' && address ? address.port : port
writeJsonLine({ type: 'relay:ready', port: boundPort })
})
server.on('error', error => {
writeJsonLine({ type: 'relay:error', message: error.message })
})
}
function start() {
const args = parseArgs()
const relayPort = Number.parseInt(process.env.ORCHESTRATE_WS_PORT || '', 10)
if (Number.isInteger(relayPort)) startRelay(relayPort)
runTick()
if (!args.once) setInterval(runTick, args.interval)
}
start()