Require reply anchors for stale Face threads
This commit is contained in:
parent
99dcd13bad
commit
cd8d1728f0
4 changed files with 50 additions and 0 deletions
|
|
@ -1208,6 +1208,11 @@ async function postRepoIdentityIntent(job: JobRecord, intent: RepoIdentityPostIn
|
|||
`Coerced repo identity ${identity.id} speech for job ${job.id} from "${requestedChannelId}" to conversation channel ${channelId}.`,
|
||||
);
|
||||
}
|
||||
if (intent.requiresExplicitReplyTo && !intent.replyToMessageId) {
|
||||
throw new Error(
|
||||
`Repo identity ${identity.id} SAY for job ${job.id} revived older context but did not include an explicit reply_to anchor.`,
|
||||
);
|
||||
}
|
||||
const replyToMessageId = intent.replyToMessageId ?? repoIdentityDefaultReplyToMessageId(job, channelId);
|
||||
const rawContent = intent.content.trim();
|
||||
if (isNonPublicRepoIdentitySpeech(rawContent)) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export interface RepoIdentityPostIntent {
|
|||
channelId?: string;
|
||||
content: string;
|
||||
replyToMessageId?: string;
|
||||
requiresExplicitReplyTo?: boolean;
|
||||
}
|
||||
|
||||
type RepoFaceActionBlock = {
|
||||
|
|
@ -13,6 +14,7 @@ type RepoFaceActionBlock = {
|
|||
};
|
||||
|
||||
export function parseRepoIdentityPostIntents(finalResponse: string): RepoIdentityPostIntent[] {
|
||||
const requiresExplicitReplyTo = repoFaceInterpretationRequiresExplicitReplyAnchor(finalResponse);
|
||||
const intents: RepoIdentityPostIntent[] = parseRepoFaceActionBlocks(finalResponse)
|
||||
.filter((block) => block.kind === "say")
|
||||
.flatMap((block): RepoIdentityPostIntent[] => {
|
||||
|
|
@ -25,6 +27,7 @@ export function parseRepoIdentityPostIntents(finalResponse: string): RepoIdentit
|
|||
identity: optionalDslString(block.fields.identity),
|
||||
channelId: optionalDslString(block.fields.channel) ?? optionalDslString(block.fields.channelId),
|
||||
replyToMessageId: optionalDslString(block.fields.reply_to) ?? optionalDslString(block.fields.replyToMessageId),
|
||||
requiresExplicitReplyTo,
|
||||
content,
|
||||
},
|
||||
];
|
||||
|
|
@ -48,6 +51,7 @@ export function parseRepoIdentityPostIntents(finalResponse: string): RepoIdentit
|
|||
channelId: typeof parsed.channelId === "string" ? parsed.channelId.trim() : undefined,
|
||||
replyToMessageId:
|
||||
typeof parsed.replyToMessageId === "string" ? parsed.replyToMessageId.trim() : undefined,
|
||||
requiresExplicitReplyTo,
|
||||
content,
|
||||
});
|
||||
} catch {
|
||||
|
|
@ -58,6 +62,20 @@ export function parseRepoIdentityPostIntents(finalResponse: string): RepoIdentit
|
|||
return intents;
|
||||
}
|
||||
|
||||
export function repoFaceInterpretationRequiresExplicitReplyAnchor(finalResponse: string): boolean {
|
||||
const interpretation = finalResponse.match(/INTERPRETATION\s*([\s\S]*?)\s*END/i)?.[1] ?? "";
|
||||
if (!interpretation.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
/\bnewest\b[\s\S]{0,160}\b(moved|elsewhere|topic)\b/i,
|
||||
/\bolder side thread\b/i,
|
||||
/\brevives?\b[\s\S]{0,120}\b(older|stale|side thread)\b/i,
|
||||
/\bstale\b[\s\S]{0,120}\b(room|context|topic|thread)\b/i,
|
||||
].some((pattern) => pattern.test(interpretation));
|
||||
}
|
||||
|
||||
export function isNonPublicRepoIdentitySpeech(value: string): boolean {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,32 @@ const cases: Case[] = [
|
|||
"show \"rear pair drifting 38 ms\", \"geometry confidence low\", \"world model provisional\".",
|
||||
detail: "Repo Faces can mention technical labels without speaking in code spans.",
|
||||
},
|
||||
{
|
||||
name: "older side-thread route requires explicit reply anchor",
|
||||
run: () => {
|
||||
const posts = parseRepoIdentityPostIntents([
|
||||
"INTERPRETATION",
|
||||
"correction_check: retry",
|
||||
"doctrine_check: none",
|
||||
"decision: route",
|
||||
"reason:",
|
||||
" The newest room context has moved to CNC workflow; this revives an older side thread.",
|
||||
"END",
|
||||
"",
|
||||
"SAY",
|
||||
"identity: current_face_id",
|
||||
"channel: current_room",
|
||||
"reply_to:",
|
||||
"content:",
|
||||
" A stale field can lie very politely.",
|
||||
"END",
|
||||
].join("\n"));
|
||||
return posts.length === 1 &&
|
||||
posts[0]?.requiresExplicitReplyTo === true &&
|
||||
posts[0]?.replyToMessageId === undefined;
|
||||
},
|
||||
detail: "A routed old-topic reply without reply_to must be marked so the worker refuses an orphan post.",
|
||||
},
|
||||
];
|
||||
|
||||
const failures = cases.filter((entry) => !entry.run());
|
||||
|
|
|
|||
|
|
@ -344,3 +344,4 @@
|
|||
{"ts":"2026-06-04T19:28:18Z","type":"implementation","status":"ok","note":"Moderation consequences now live in typed void.moderation_cursor userStatuses with one-week strike expiry and pending user-status notices; unattended Discord egress defaults through Bifrost bridge posts/DMs with direct VoidBot send helper retained only behind VOID_DISCORD_TRANSPORT=direct fallback."}
|
||||
{"ts":"2026-06-04T20:43:18Z","type":"verification","status":"ok","note":"Rumination parent normalizes legacy top-level userStatuses model output into upsert_moderation_user_status operations; fixtures cover the stale shape and moderation status DM delivery."}
|
||||
{"ts":"2026-06-04T21:00:32Z","type":"implementation","status":"ok","note":"Legacy bare moderation cursor documents with top-level userStatuses now decode through voidModerationCursorSchema into canonical cursor shape; verified build, state-boundary fixture, rumination fixture, targeted bot/worker restart, and restored a lost Nibu summon through the heartbeat path (Discord message 1512199001309446295). Separate CultCache/CultMesh Extra-byte decode still remains if it recurs outside state-note-only jobs."}
|
||||
{"ts":"2026-06-04T22:20:09Z","type":"implementation","status":"ok","note":"Repo Face SAY routing now marks parent-Interpreter stale/older-side-thread routes as requiring an explicit reply_to anchor; worker refuses those SAY intents instead of defaulting to an unanchored current_room post. Verified build, agents:speech-boundary-smoke, moderation state-boundary fixture, and restarted worker PID 2488."}
|
||||
|
|
|
|||
Loading…
Reference in a new issue