Files
Investidor-V2/tools/portal-audit-sector-results.mjs
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

207 lines
7.8 KiB
JavaScript

// Patch the existing CSV cells using Artifact Tool; never create a second requirements matrix.
import fs from 'node:fs/promises';
import path from 'node:path';
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { pathToFileURL, fileURLToPath } from 'node:url';
import crypto from 'node:crypto';
const ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
const DOC = path.join(ROOT, 'docs/investmoz/v2/auditoria-portal');
const PRIVATE = path.join(
ROOT,
'storage/app/private/investmoz-v2/portal-audit',
);
const runtime = path.join(PRIVATE, 'legacy-sector-reconciliation-20261010');
const requireRuntime = createRequire(path.join(runtime, 'package.json'));
const { Workbook } = await import(
pathToFileURL(requireRuntime.resolve('@oai/artifact-tool')).href
);
const packetName = process.argv[2] || 'legacy-sectors-finalize-20261010';
assert(
/^[a-z0-9-]+$/.test(packetName),
'Editing packet must remain inside the private audit directory',
);
const packetDirectory = path.join(PRIVATE, packetName);
const packet = JSON.parse(
await fs.readFile(path.join(packetDirectory, 'csv-edits.json'), 'utf8'),
);
const sha = (b) => crypto.createHash('sha256').update(b).digest('hex');
const serialize = (rows) =>
'\ufeff' +
rows
.map((row) =>
row
.map((v) =>
/[",\r\n]/.test(v)
? '"' + v.replaceAll('"', '""') + '"'
: v,
)
.join(','),
)
.join('\r\n') +
'\r\n';
const matches = (value, literal) =>
value instanceof Date
? value.getTime() === Date.parse(literal)
: (value == null ? '' : String(value)) === literal;
const outputs = [];
for (const edit of packet.files) {
const filename = edit.filename,
file = path.join(DOC, filename),
input = await fs.readFile(file, 'utf8');
assert.equal(
sha(Buffer.from(input)),
edit.input_sha256,
'Source changed since targeted editing was prepared',
);
const source = edit.rows,
strings = source.map((row) => [...row]);
const workbook = await Workbook.fromCSV(input.replace(/^\ufeff/, ''), {
sheetName: 'Registos',
});
const sheet = workbook.worksheets.getItem('Registos'),
headers = source[0];
const observed = sheet.getRangeByIndexes(
0,
0,
source.length,
headers.length,
).values;
assert(
observed.every((r, i) => r.every((v, c) => matches(v, source[i][c]))),
'Imported cells must match existing source',
);
const changes = [];
for (const patch of edit.patches) {
const row = patch.row;
assert(row > 0 && row < source.length);
const updated = [...strings[row]];
for (const [field, value] of Object.entries(patch.values)) {
const col = headers.indexOf(field);
assert(col >= 0 && edit.allowed_columns.includes(field));
updated[col] = value;
}
sheet.getRangeByIndexes(row, 0, 1, headers.length).values = [updated];
strings[row] = updated;
changes.push({ row: row + 1, fields: Object.keys(patch.values) });
}
if (edit.remove_rows?.length) {
assert.equal(filename, 'inventario-fontes.csv', 'Only redundant source alias rows may be consolidated');
for (const removal of [...edit.remove_rows].sort((a, b) => b.row - a.row)) {
assert(removal.row > 0 && removal.row < strings.length);
assert.equal(strings[removal.row][headers.indexOf('SHA256')], removal.sha256);
assert.equal(strings[removal.row][headers.indexOf('Exemplar/caminho')], removal.path);
assert(strings.some((row, index) => index > 0 && index !== removal.row &&
row[headers.indexOf('SHA256')] === removal.sha256 &&
row[headers.indexOf('Exemplar/caminho')] === removal.canonical_path), 'Canonical source must remain');
strings.splice(removal.row, 1);
}
sheet.getRangeByIndexes(0, 0, strings.length, headers.length).values = strings;
}
if (edit.append?.length) {
assert(
['backlog-priorizado.csv', 'matriz-requisitos.csv', 'inventario-fontes.csv'].includes(filename),
'Append only to an existing canonical registry or its backlog projection',
);
assert(edit.append.every(row => row.length === headers.length && row.every(value => typeof value === 'string')));
const identityColumns = filename === 'inventario-fontes.csv'
? ['Exemplar/caminho', 'SHA256'] : ['ID'];
const key = row => identityColumns.map(column => row[headers.indexOf(column)]).join('|');
const identifiers = new Set(strings.slice(1).map(key));
for (const row of edit.append) {
assert(!identifiers.has(key(row)), 'Appended registry entry already exists');
identifiers.add(key(row));
}
sheet.getRangeByIndexes(
strings.length,
0,
edit.append.length,
headers.length,
).values = edit.append;
strings.push(...edit.append);
}
// Source registry hashes refer to the final bytes authored above; original hashes remain in version history.
if (filename === 'inventario-fontes.csv') {
for (const patch of edit.source_hash_updates) {
const row = patch.row,
exemplar = strings[row][headers.indexOf('Exemplar/caminho')];
const final = outputs.find(
(out) =>
path.normalize(path.join(DOC, out.filename)) ===
path.normalize(exemplar),
);
const bytes = final
? Buffer.from(final.text)
: await fs.readFile(exemplar);
strings[row][headers.indexOf('SHA256')] = sha(bytes);
strings[row][headers.indexOf('Bytes')] = String(bytes.length);
sheet.getRangeByIndexes(row, 0, 1, headers.length).values = [
strings[row],
];
}
}
workbook.recalculate();
const result = sheet.getRangeByIndexes(
0,
0,
strings.length,
headers.length,
).values;
assert(
result.every((r, i) => r.every((v, c) => matches(v, strings[i][c]))),
'Authored fields differ from inspected workbook cells',
);
const inspected = await workbook.inspect({
kind: 'table',
range: 'Registos!A1:C3',
include: 'values',
tableMaxRows: 3,
tableMaxCols: 3,
});
assert(inspected.ndjson);
// Preserve all original literal identifiers/offset dates. CSV has no workbook formatting or rendering surface.
const text = serialize(strings),
temporary = path.join(packetDirectory, filename + '.verified');
await fs.writeFile(temporary, text, 'utf8');
outputs.push({
filename,
text,
temporary,
changes,
appended: edit.append?.length || 0,
});
}
// Commit only after every table and dependency has passed preservation checks.
for (const out of outputs)
await fs.copyFile(out.temporary, path.join(DOC, out.filename));
await fs.writeFile(
path.join(
packetDirectory,
packet.proof_filename || 'csv-authoring-proof.json',
),
JSON.stringify(
{
database_access: false,
changed: outputs.map(({ filename, text, changes, appended }) => ({
filename,
sha256: sha(Buffer.from(text)),
changes,
appended,
})),
},
null,
2,
),
);
console.log(
JSON.stringify({
tables: outputs.length,
changed: outputs.map(({ filename, changes, appended }) => ({
filename,
rows: changes.length,
appended,
})),
database_writes: 0,
}),
);