Extract moderation evidence source

This commit is contained in:
Meta Vens 2026-07-16 05:00:14 +01:00
commit 5b9bfd6a85
2 changed files with 91 additions and 0 deletions

View file

@ -0,0 +1,81 @@
import { execFile } from "node:child_process";
import { resolve } from "node:path";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
export interface ModerationHistoryMessage {
id: string;
timestamp: string;
channelId?: string;
authorId?: string;
authorName?: string;
content?: string;
[key: string]: unknown;
}
export interface VoidModerationEvidence {
status: "ok";
after?: string;
hours?: number;
totalMatchingMessages: number;
returnedMessages: number;
messages: ModerationHistoryMessage[];
observedCursor?: { lastReviewedMessageId: string; lastReviewedTimestamp: string };
}
export async function readVoidModerationEvidence(input: {
priorCursorTimestamp?: string;
limit?: number;
fallbackHours?: number;
scriptPath?: string;
nodeExecutable?: string;
}, dependencies: {
run?: (file: string, args: string[], options: { cwd: string; maxBuffer: number }) => Promise<{ stdout: string }>;
} = {}): Promise<VoidModerationEvidence> {
const scriptPath = resolve(input.scriptPath ?? "scripts/export-recent-discord-history.mjs");
const args = [scriptPath];
if (input.priorCursorTimestamp) args.push("--after", input.priorCursorTimestamp);
else args.push("--hours", String(input.fallbackHours ?? 6));
args.push("--limit", String(input.limit ?? 120));
const run = dependencies.run ?? (async (file, commandArgs, options) => execFileAsync(file, commandArgs, options));
const { stdout } = await run(input.nodeExecutable ?? process.execPath, args, { cwd: process.cwd(), maxBuffer: 4 * 1024 * 1024 });
let raw: unknown;
try { raw = JSON.parse(stdout); } catch (error) { throw new Error(`Moderation evidence exporter returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`); }
const parsed = parseExporterResult(raw);
const latest = parsed.messages.at(-1);
return {
status: "ok",
after: parsed.after,
hours: parsed.hours,
totalMatchingMessages: parsed.totalMatchingMessages,
returnedMessages: parsed.messages.length,
messages: parsed.messages,
observedCursor: latest ? { lastReviewedMessageId: latest.id, lastReviewedTimestamp: latest.timestamp } : undefined,
};
}
function parseExporterResult(raw: unknown): {
after?: string;
hours?: number;
totalMatchingMessages: number;
messages: ModerationHistoryMessage[];
} {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Moderation evidence exporter result must be an object.");
const value = raw as Record<string, unknown>;
if (!Array.isArray(value.messages)) throw new Error("Moderation evidence exporter result has no messages array.");
const messages = value.messages.map((entry, index) => {
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new Error(`Moderation evidence message ${index + 1} must be an object.`);
const message = entry as Record<string, unknown>;
if (typeof message.id !== "string" || !message.id || typeof message.timestamp !== "string" || !Number.isFinite(Date.parse(message.timestamp))) {
throw new Error(`Moderation evidence message ${index + 1} requires id and valid timestamp.`);
}
return message as ModerationHistoryMessage;
});
return {
after: typeof value.after === "string" ? value.after : undefined,
hours: typeof value.hours === "number" ? value.hours : undefined,
totalMatchingMessages: typeof value.totalMatchingMessages === "number" ? value.totalMatchingMessages : messages.length,
messages,
};
}

View file

@ -85,6 +85,7 @@ import { buildVoidMemoryMaintenancePrompt, parseVoidMemoryMaintenanceOperations,
import { runVoidMemoryMaintenance } from "../apps/persona-scheduler/dist/void-memory-maintenance-organ.js";
import { projectVoidMemoryOperations } from "../apps/persona-scheduler/dist/void-memory-text-actuator.js";
import { buildVoidModerationHeartbeatPrompt, parseVoidModerationHeartbeatOperations, projectVoidModerationHeartbeatContext } from "../apps/persona-scheduler/dist/void-moderation-heartbeat-projector.js";
import { readVoidModerationEvidence } from "../apps/persona-scheduler/dist/void-moderation-evidence-source.js";
import { projectGamecultPersonaState } from "../apps/persona-scheduler/dist/persona-standard-state-projector.js";
import { projectPersonaStatePacket } from "../apps/persona-scheduler/dist/persona-state-packet-projector.js";
import { extractLastPersonaProjectionMessage, isRetryablePersonaProjectionFailure } from "../apps/persona-scheduler/dist/persona-text-projection-actuator.js";
@ -860,6 +861,15 @@ assert.throws(() => parseVoidModerationHeartbeatOperations({ outputText: JSON.st
},
}]), state: physiologyState }), /exactly one infringement/, "moderation classification cannot multiply infringement authority for one message");
assert.throws(() => parseVoidModerationHeartbeatOperations({ outputText: JSON.stringify([{ operation: "update_sleep_cycle", sleepCycle: physiology.sleepCycle }]), state: physiologyState }), /not allowed/, "moderation cannot annex physiology even when the operation is schema-valid");
let moderationExporterArgs: string[] = [];
const moderationEvidence = await readVoidModerationEvidence({ priorCursorTimestamp: "2026-07-16T02:00:00.000Z" }, {
run: async (_file, args) => {
moderationExporterArgs = args;
return { stdout: JSON.stringify({ after: "2026-07-16T02:00:00.000Z", totalMatchingMessages: 1, messages: [{ id: "message-1", timestamp: "2026-07-16T02:55:00.000Z", channelId: "room-1", content: "fixture" }] }) };
},
});
assert.deepEqual(moderationExporterArgs.slice(-4), ["--after", "2026-07-16T02:00:00.000Z", "--limit", "120"], "moderation evidence acquisition owns cursor-bounded chronological export arguments");
assert.deepEqual(moderationEvidence.observedCursor, { lastReviewedMessageId: "message-1", lastReviewedTimestamp: "2026-07-16T02:55:00.000Z" }, "moderation evidence returns the exact parent-owned cursor witness separately from prompt chronology");
const maintainedState = structuredClone(physiologyState);
let memoryModelCalls = 0;
const memoryMaintenanceDependencies = {