Files
sabia/.opencode/plugin/openwolf/event-journal.ts
T
Lucas e989413018 Esqueleto inicial: sabia-core + sabia-cli + UI local, OpenWolf
- sabia-core: descoberta de interfaces, controle de modo monitor/canal
  via iw (ponte até ter nl80211 nativo), captura crua 802.11 via
  AF_PACKET (sem libpcap), parsing de radiotap e quadros de
  gerenciamento (beacon/probe/EAPOL), agregação em tabela de APs/
  clientes, escrita de .pcap, trilha de auditoria encadeada por hash,
  auto-atualização via GitHub releases (curl, sem dependências de
  rede extras)
- sabia-cli: `sabia iface`, `sabia scan`, `sabia update`,
  `sabia audit-verify`, `sabia ui` (servidor local + navegador,
  protegido por token, mesma filosofia do Guará)
- OpenWolf inicializado para memória de projeto entre agentes
2026-10-23 08:22:04 -03:00

77 lines
3.3 KiB
TypeScript

import * as fs from "node:fs";
import * as path from "node:path";
import * as crypto from "node:crypto";
import { mutateJSON, HOOK_LOCK_BUDGET_MS } from "./anatomy-lock.js";
export interface ReadObservation {
id: string;
file: string;
at: string;
tokens: number;
fingerprint?: string;
mtime?: number;
internal?: boolean;
kind?: "read" | "write";
action?: string;
editKey?: string;
}
/** One immutable event per file: no contended append, and no shared offset. */
export function persistRead(sessionFile: string, event: ReadObservation): void {
const dir = sessionFile + ".events";
fs.mkdirSync(dir, { recursive: true });
const dest = path.join(dir, crypto.createHash("sha256").update(event.id).digest("hex") + ".json");
const tmp = dest + "." + crypto.randomUUID() + ".tmp";
try {
const fd = fs.openSync(tmp, "wx", 0o600);
try { fs.writeFileSync(fd, JSON.stringify(event)); fs.fsyncSync(fd); } finally { fs.closeSync(fd); }
// A retry of the same upstream event cannot replace an already persisted event.
try { fs.linkSync(tmp, dest); } catch (e) { if ((e as NodeJS.ErrnoException).code !== "EEXIST") throw e; }
} finally { try { fs.unlinkSync(tmp); } catch {} }
}
export function reconcileReads(sessionFile: string, budget = HOOK_LOCK_BUDGET_MS): boolean {
const dir = sessionFile + ".events";
let names: string[];
try { names = fs.readdirSync(dir).filter(n => n.endsWith(".json")); } catch { return true; }
if (!names.length) return true;
const done: string[] = [];
const result = mutateJSON<Record<string, any>>(sessionFile, { files_read: {} }, budget, s => {
s.applied_read_events ??= {};
s.files_read ??= {};
const events = names.flatMap(name => {
try { return [{ name, event: JSON.parse(fs.readFileSync(path.join(dir, name), "utf8")) as ReadObservation }]; }
catch (e) { if ((e as NodeJS.ErrnoException).code === "ENOENT") return []; throw e; }
}).sort((a,b) => a.event.at.localeCompare(b.event.at));
for (const { name, event: e } of events) {
if (!s.applied_read_events[e.id]) {
if (e.kind === "write") {
s.files_written ??= [];
s.edit_counts ??= {};
s.files_written.push({file:e.file,action:e.action ?? "edit",tokens:e.tokens,at:e.at});
const key=e.editKey ?? e.file;
s.edit_counts[key]=(s.edit_counts[key] ?? 0)+1;
delete s.files_read[e.file];
} else if (e.internal) {
s.wolf_internal_tokens = (s.wolf_internal_tokens ?? 0) + e.tokens;
s.wolf_internal_reads ??= {};
s.wolf_internal_reads[e.file] = (s.wolf_internal_reads[e.file] ?? 0) + e.tokens;
} else {
const previous = s.files_read[e.file];
if (!previous?.observed_at || previous.observed_at <= e.at) {
s.files_read[e.file] = { ...previous, count: previous?.count ?? 1, tokens: e.tokens, first_read: previous?.first_read ?? e.at, observed_at: e.at, ranged: false, read_hash: e.fingerprint, read_mtime: e.mtime };
}
}
s.applied_read_events[e.id] = true;
}
done.push(name);
}
});
if (result === null) return false;
// Receipts remain in the state, so a crash between commit and unlink is safe.
for (const name of done) { try { fs.unlinkSync(path.join(dir, name)); } catch {} }
return true;
}
export const persistObservation = persistRead;