72 lines
5.1 KiB
JavaScript
72 lines
5.1 KiB
JavaScript
/** Offline audit of retained REA exports. Never opens the original package. */
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import assert from 'node:assert/strict'
|
|
import crypto from 'node:crypto'
|
|
import { parse } from 'yaml'
|
|
|
|
const root = path.resolve('.tmp/rea-ui-designer-recovered-20261016')
|
|
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'evidence-manifest.json'), 'utf8'))
|
|
const pin = 'c9c83df190f0f0f871706d52b7e372f20b7ad63fde739f59cf9181ace1d08afa'
|
|
const inventory = JSON.parse(fs.readFileSync(manifest.retainedInventory, 'utf8'))
|
|
const sha = bytes => crypto.createHash('sha256').update(bytes).digest('hex')
|
|
assert.equal(manifest.rootSha256, pin)
|
|
assert.equal(inventory.normalized_result.root_sha256, pin)
|
|
assert.equal(inventory.evidence_id, manifest.inventoryEvidenceId)
|
|
assert.equal(inventory.normalized_result.coverage.status, 'complete')
|
|
assert.equal(manifest.remaining.length, 0)
|
|
assert.equal(manifest.files.length, 143)
|
|
assert.deepEqual(manifest.excluded.map(item => item.path), ['.downloaded_at'])
|
|
const expected = inventory.normalized_result.members.filter(item => item.kind === 'file' && item.path !== '.downloaded_at')
|
|
assert.deepEqual(manifest.files.map(item => item.path).sort(), expected.map(item => item.path).sort())
|
|
const evidence = new Map(), checks = []
|
|
for (const file of manifest.files) {
|
|
const destination = path.resolve(root, file.path), relative = path.relative(root, destination)
|
|
assert(!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith('..' + path.sep))
|
|
const bytes = fs.readFileSync(destination), source = expected.find(item => item.path === file.path)
|
|
assert.equal(bytes.length, source.size_bytes, file.path)
|
|
assert.equal(sha(bytes), source.sha256, file.path)
|
|
assert.equal(file.bytes, bytes.length, file.path)
|
|
assert.equal(file.sha256, source.sha256, file.path)
|
|
if (!evidence.has(file.retainedExport)) evidence.set(file.retainedExport, JSON.parse(fs.readFileSync(file.retainedExport, 'utf8')))
|
|
const exported = evidence.get(file.retainedExport)
|
|
assert.equal(exported.evidence_id, file.evidenceId)
|
|
assert.equal(exported.normalized_result.root_sha256, pin)
|
|
const selected = exported.normalized_result.files.find(item => item.path === file.path)
|
|
assert(selected, file.path)
|
|
const selectedBytes = file.encoding === 'base64' ? Buffer.from(selected.data_base64, 'base64') : Buffer.from(selected.text, 'utf8')
|
|
assert.equal(selected.sha256, source.sha256, file.path)
|
|
assert.equal(selected.size_bytes, bytes.length, file.path)
|
|
assert(bytes.equals(selectedBytes), file.path)
|
|
checks.push({ path: file.path, bytes: bytes.length, sha256: sha(bytes), evidenceId: exported.evidence_id, retainedExport: file.retainedExport, pass: true })
|
|
}
|
|
const read = file => fs.readFileSync(path.join(root, file), 'utf8')
|
|
const frontmatter = file => parse(read(file).match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1] ?? '')
|
|
const plugin = JSON.parse(read('.codebuddy-plugin/plugin.json'))
|
|
assert.equal(plugin.name, 'ui-designer')
|
|
assert.deepEqual(plugin.skills, ['./skills/impeccable', './skills/frontend-dev', './skills/browser-use'])
|
|
const skills = plugin.skills.map(file => ({ path: file, metadata: frontmatter(file + '/SKILL.md') }))
|
|
for (const file of plugin.agents) assert(manifest.files.some(item => './' + item.path === file))
|
|
for (const skill of plugin.skills) assert(manifest.files.some(item => './' + item.path === skill + '/SKILL.md'))
|
|
assert(!manifest.files.some(item => item.path.endsWith('.mcp.json')))
|
|
const miniMaxScripts = manifest.files.filter(item => /frontend-dev\/scripts\/.*\.py$/.test(item.path)).map(item => {
|
|
const source = read(item.path)
|
|
assert(source.includes('import requests'))
|
|
for (const key of ['MINIMAX_API_KEY', 'MINIMAX_API_BASE']) assert(source.includes(`os.getenv("${key}")`))
|
|
return { path: item.path, requires: ['Python', 'requests', 'MINIMAX_API_KEY', 'MINIMAX_API_BASE'] }
|
|
})
|
|
assert.equal(miniMaxScripts.length, 4)
|
|
const html = read('skills/frontend-dev/templates/viewer.html')
|
|
const templateRemoteUrls = [...new Set(html.match(/https:\/\/[^"'<>\s]+/g))]
|
|
const groups = Object.fromEntries(['.ttf', '.png', '.LICENSE', '.html', '.py'].map(extension => {
|
|
const files = manifest.files.filter(item => item.path.endsWith(extension))
|
|
return [extension, { files: files.length, bytes: files.reduce((sum, item) => sum + item.bytes, 0) }]
|
|
}))
|
|
const report = { rootSha256: pin, inventoryEvidenceId: inventory.evidence_id, packageVersion: plugin.version,
|
|
functionalFiles: checks.length, functionalBytes: checks.reduce((sum, item) => sum + item.bytes, 0),
|
|
excluded: manifest.excluded, remaining: 0, evidenceExports: evidence.size, groups, plugin, skills, miniMaxScripts,
|
|
templateRemoteUrls, checks, scope: 'REA byte recovery and offline dependency analysis; no execution, installation, remote request, or original runtime behavior verification' }
|
|
const reportPath = path.resolve('.tmp/rea-ui-designer-audit-20261016.json')
|
|
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + '\n')
|
|
console.log(JSON.stringify({ report: reportPath, rootSha256: pin, verified: checks.length, bytes: report.functionalBytes, remaining: 0, exports: evidence.size, groups, templateRemoteUrls }, null, 2))
|