feat: add system disclosure policy gate
Memind CI / Test, build, and release guards (pull_request) Successful in 3m14s

This commit is contained in:
john
2026-07-23 22:53:15 +08:00
parent ba6cc10f08
commit 205b5fd8be
20 changed files with 1585 additions and 2 deletions
+64 -1
View File
@@ -283,6 +283,7 @@ export function createAgentRunGateway({
tkmindProxy,
toolGateway = null,
directChatService = null,
systemDisclosurePolicyService = null,
chatIntentRouter = null,
sessionSnapshotService = null,
conversationMemoryService = null,
@@ -596,6 +597,60 @@ export function createAgentRunGateway({
let userMessage = safeJsonParse(row.user_message_json, {});
const runOptions = getRunOptionsFromMessage(userMessage);
const toolGatewayStatus = toolGateway?.getStatus ? toolGateway.getStatus() : null;
let disclosureDecision = null;
try {
disclosureDecision = systemDisclosurePolicyService?.evaluate?.({
userMessage,
channel: 'h5',
userId: row.user_id,
sessionId: row.agent_session_id ?? null,
}) ?? null;
} catch (err) {
console.warn(
'[AgentRun] system disclosure policy evaluation failed open:',
err instanceof Error ? err.message : err,
);
}
if (disclosureDecision?.enforced) {
if (!directChatService?.respondDeterministically) {
const error = new Error('System Disclosure Policy refusal service unavailable');
error.code = 'SYSTEM_DISCLOSURE_REFUSAL_UNAVAILABLE';
error.retryable = false;
throw error;
}
const result = await directChatService.respondDeterministically({
userId: row.user_id,
sessionId: row.agent_session_id ?? null,
requestId: row.request_id,
userMessage,
reply: disclosureDecision.responseText,
metadata: {
policyId: disclosureDecision.policyId,
policyVersion: disclosureDecision.policyVersion,
policyReasonCode: disclosureDecision.reasonCode,
},
onSessionReady: async (activeSessionId) => {
await pool.query(
`UPDATE h5_agent_runs SET agent_session_id = ?, updated_at = ? WHERE id = ?`,
[activeSessionId, nowMs(), runId],
);
await appendRunSnapshot(runId);
},
});
await appendEvent(runId, 'system_disclosure_blocked', {
policyId: disclosureDecision.policyId,
policyVersion: disclosureDecision.policyVersion,
reasonCode: disclosureDecision.reasonCode,
categories: disclosureDecision.categories,
channel: 'h5',
});
return {
sessionId: result.sessionId,
routing: null,
toolEvidence: null,
policyBlocked: true,
};
}
const routing = await resolveRunRouting(row, userMessage, runOptions);
const routingDecision = resolveLegacyRouteFromClassification(routing) ?? routing?.route ?? null;
let agentMemoryContext = null;
@@ -867,8 +922,16 @@ export function createAgentRunGateway({
runId,
row,
sessionId,
{ routing = null, toolEvidence = null } = {},
{ routing = null, toolEvidence = null, policyBlocked = false } = {},
) {
if (policyBlocked) {
await markRun(runId, 'succeeded', {
agent_session_id: sessionId,
completed_at: nowMs(),
error_message: null,
}, { expectedStatus: 'running' });
return;
}
assertRequiredImageGenerationCompleted(row, routing, toolEvidence);
// `row` was loaded before this worker claimed the run, so its started_at
// can still be null. Refresh it before scoping workspace files to the