From 4b9550ce06bcf3ba1b43d7690a3d3f27ab7bcc35 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 16 Jul 2026 04:57:06 +0100 Subject: [PATCH] Extract moderation heartbeat projection --- .../void-moderation-heartbeat-projector.ts | 102 ++++++++++++++++++ scripts/smoke-persona-initiative-engine.ts | 29 +++++ 2 files changed, 131 insertions(+) create mode 100644 apps/persona-scheduler/src/void-moderation-heartbeat-projector.ts diff --git a/apps/persona-scheduler/src/void-moderation-heartbeat-projector.ts b/apps/persona-scheduler/src/void-moderation-heartbeat-projector.ts new file mode 100644 index 0000000..4df19ea --- /dev/null +++ b/apps/persona-scheduler/src/void-moderation-heartbeat-projector.ts @@ -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 = (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: 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: 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"); +} diff --git a/scripts/smoke-persona-initiative-engine.ts b/scripts/smoke-persona-initiative-engine.ts index 95dba3d..68d1e89 100644 --- a/scripts/smoke-persona-initiative-engine.ts +++ b/scripts/smoke-persona-initiative-engine.ts @@ -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 = {