Extract moderation heartbeat projection

This commit is contained in:
Meta Vens 2026-07-16 04:57:06 +01:00
commit 4b9550ce06
2 changed files with 131 additions and 0 deletions

View file

@ -0,0 +1,102 @@
import { voidSelfStateOperationSchema, type VoidSelfStateOperation, type VoidSelfStateTypedProjection } from "@voidbot/core";
const ALLOWED_MODERATION_TAGS = new Set(["moderation:instaban", "moderation:strike", "moderation:case_only"]);
export interface VoidModerationHeartbeatContext {
mode: "moderation_heartbeat";
operationTimestamp: string;
enforcementMode: string;
rules: string;
priorCursor: unknown;
observedCursor: unknown;
openCases: unknown[];
urgentModerationWitnesses: unknown[];
recentHistory: unknown;
}
export function projectVoidModerationHeartbeatContext(input: {
state: VoidSelfStateTypedProjection;
observedAt: Date;
observedCursor: unknown;
recentHistory: unknown;
urgentModerationWitnesses: unknown[];
enforcementMode: string;
rules: string;
}): VoidModerationHeartbeatContext {
const relative = <T>(value: T): T => projectRelativeChronology(value, input.observedAt) as T;
return {
mode: "moderation_heartbeat",
operationTimestamp: input.observedAt.toISOString(),
enforcementMode: input.enforcementMode,
rules: input.rules,
priorCursor: relative(input.state.moderationCursor),
observedCursor: relative(input.observedCursor),
openCases: relative(input.state.moderationCursor.openCases),
urgentModerationWitnesses: relative(input.urgentModerationWitnesses),
recentHistory: relative(input.recentHistory),
};
}
export function buildVoidModerationHeartbeatPrompt(context: VoidModerationHeartbeatContext): string {
return [
"You are Void's rules-only public-community moderation organ. This is not public speech, private rumination, or repo thought.",
"Return only a JSON array of complete typed Void self-state operation objects. Do not use markdown fences, commentary, tools, or file writes.",
"Allowed operations: upsert_open_case, close_open_case.",
"Each new case must identify one concrete source message and contain exactly one infringement:<type> tag plus exactly one moderation:instaban, moderation:strike, or moderation:case_only tag.",
"Choose the strongest supported infringement type; never multiply cases or sanctions for one message.",
"Do not reopen an already-actioned case. Urgent safety evidence must be accounted for by an existing case or a proposed upsert.",
"Use operationTimestamp when a fresh exact timestamp is required. Relative chronology in evidence is not schema-ready timestamp text.",
"Moderation context:",
JSON.stringify(context, null, 2),
].join("\n\n");
}
export function parseVoidModerationHeartbeatOperations(input: {
outputText: string;
state: VoidSelfStateTypedProjection;
}): VoidSelfStateOperation[] {
const trimmed = input.outputText.trim();
const candidate = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i)?.[1]?.trim() ?? trimmed;
let raw: unknown;
try { raw = JSON.parse(candidate); } catch (error) { throw new Error(`Moderation heartbeat output is not valid JSON: ${error instanceof Error ? error.message : String(error)}`); }
if (!Array.isArray(raw)) throw new Error("Moderation heartbeat output must be a JSON array.");
return raw.map((entry, index) => validateOperation(entry, index, input.state));
}
function validateOperation(entry: unknown, index: number, state: VoidSelfStateTypedProjection): VoidSelfStateOperation {
const operation = voidSelfStateOperationSchema.parse(entry);
if (operation.operation !== "upsert_open_case" && operation.operation !== "close_open_case") {
throw new Error(`Moderation heartbeat operation ${index + 1} is not allowed: ${operation.operation}.`);
}
if (operation.operation === "close_open_case") return operation;
const prior = state.moderationCursor.openCases.find((item) => item.sourceMessageId === operation.case.sourceMessageId);
if (prior && ["answered", "resolved", "closed", "retired", "dropped"].includes(prior.status)
&& /(?:instaban|three-strike ban|strike \d\/3 recorded|ban applied)/i.test(prior.resolutionSummary ?? "")) {
throw new Error(`Moderation heartbeat cannot reopen already-actioned case '${operation.case.sourceMessageId}'.`);
}
const tags = operation.case.tags.map((tag) => tag.toLowerCase());
const infringementTags = tags.filter((tag) => tag.startsWith("infringement:"));
const moderationTags = tags.filter((tag) => ALLOWED_MODERATION_TAGS.has(tag));
if (infringementTags.length !== 1) throw new Error("Moderation heartbeat open cases must include exactly one infringement:<type> tag.");
if (moderationTags.length !== 1) throw new Error("Moderation heartbeat open cases must include exactly one moderation classification tag.");
return operation;
}
function projectRelativeChronology(value: unknown, observedAt: Date, key = ""): unknown {
if (Array.isArray(value)) return value.map((entry) => projectRelativeChronology(entry, observedAt));
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([entryKey, entry]) => [entryKey, projectRelativeChronology(entry, observedAt, entryKey)]));
if (typeof value === "string" && /(?:at|timestamp)$/i.test(key) && Number.isFinite(Date.parse(value))) return relativeTime(value, observedAt);
return value;
}
function relativeTime(value: string, observedAt: Date): string {
const difference = observedAt.getTime() - Date.parse(value);
const future = difference < 0;
const minutes = Math.max(0, Math.round(Math.abs(difference) / 60_000));
const phrase = (amount: number, unit: string) => future ? `in ${amount} ${unit}${amount === 1 ? "" : "s"}` : `${amount} ${unit}${amount === 1 ? "" : "s"} ago`;
if (minutes < 60) return phrase(minutes, "minute");
const hours = Math.round(minutes / 60);
if (hours < 24) return phrase(hours, "hour");
const days = Math.round(hours / 24);
return phrase(days, "day");
}

View file

@ -84,6 +84,7 @@ import { runVoidPhysiologyOrgan } from "../apps/persona-scheduler/dist/void-phys
import { buildVoidMemoryMaintenancePrompt, parseVoidMemoryMaintenanceOperations, projectVoidMemoryMaintenanceContext } from "../apps/persona-scheduler/dist/void-memory-maintenance-projector.js";
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 { 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";
@ -831,6 +832,34 @@ const parsedMaintenance = parseVoidMemoryMaintenanceOperations(JSON.stringify([{
}]));
assert.equal(parsedMaintenance[0]?.operation, "prune_short_term_memories", "memory output parsing crosses the canonical operation schema before application");
assert.throws(() => parseVoidMemoryMaintenanceOperations(JSON.stringify([{ operation: "update_sleep_cycle", sleepCycle: physiology.sleepCycle }])), /not allowed/, "memory maintenance cannot annex physiology operations even when they are schema-valid");
const moderationContext = projectVoidModerationHeartbeatContext({
state: physiologyState,
observedAt: physiologyObservedAt,
observedCursor: { lastReviewedMessageId: "message-1", lastReviewedTimestamp: physiologyObservedAt.toISOString() },
recentHistory: { messages: [{ id: "message-1", timestamp: "2026-07-16T02:55:00.000Z", content: "fixture" }] },
urgentModerationWitnesses: [],
enforcementMode: "log_only",
rules: "Fixture rules",
});
assert.match(JSON.stringify(moderationContext.recentHistory), /5 minutes ago/, "moderation evidence chronology is projected without surrendering exact operation time ownership");
assert.doesNotMatch(buildVoidModerationHeartbeatPrompt(moderationContext), /OPERATION_OUTPUT_PATH|apply-operation/, "portable moderation prompts return operations directly and cannot preserve the filesystem tool-writing contract");
const parsedModeration = parseVoidModerationHeartbeatOperations({ outputText: JSON.stringify([{
operation: "upsert_open_case",
case: {
sourceMessageId: "message-1", status: "pending", summary: "Concrete fixture evidence.", authorId: "human-1", authorName: "Human",
channelId: "room-1", whyItMatters: "The fixture crosses the safety boundary.", createdAt: physiologyObservedAt.toISOString(),
lastTouchedAt: physiologyObservedAt.toISOString(), tags: ["infringement:safety_threat", "moderation:instaban"],
},
}]), state: physiologyState });
assert.equal(parsedModeration[0]?.operation, "upsert_open_case", "moderation output parsing crosses the canonical operation schema and classification invariant");
assert.throws(() => parseVoidModerationHeartbeatOperations({ outputText: JSON.stringify([{
operation: "upsert_open_case",
case: {
sourceMessageId: "message-2", status: "watching", summary: "Ambiguous fixture evidence.", createdAt: physiologyObservedAt.toISOString(),
lastTouchedAt: physiologyObservedAt.toISOString(), tags: ["infringement:safety_threat", "infringement:weaponized_intimidation", "moderation:case_only"],
},
}]), 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");
const maintainedState = structuredClone(physiologyState);
let memoryModelCalls = 0;
const memoryMaintenanceDependencies = {