chore(sf): differentiate auto-accepted vs user-resolved escalations in audit

resolveEscalation gains an optional `source: "user" | "auto-mode"`
parameter (default "user"). Auto-dispatch passes "auto-mode" when it
auto-accepts. The UOK audit event type now flips between
"escalation-user-responded" and "escalation-auto-accepted", and the
payload includes a typed `resolvedBy` field.

Why: a journal grep for user actions shouldn't return auto-mode events.
Audit/observability tools can now filter cleanly without string-matching
the rationale prefix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-02 21:59:38 +02:00
parent 00c13bc5a1
commit c308a492d7
2 changed files with 7 additions and 1 deletions

View file

@ -548,6 +548,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
state.activeTask.id,
"accept",
"auto-mode: accepted agent recommendation; user can override via /sf escalate",
"auto-mode",
);
if (result.status === "resolved") {
// Flags cleared; let the next dispatch cycle re-read state and

View file

@ -311,6 +311,7 @@ export function resolveEscalation(
taskId: string,
choice: string,
rationale: string,
source: "user" | "auto-mode" = "user",
): ResolveEscalationResult {
const task = getTask(milestoneId, sliceId, taskId);
if (!task || !task.escalation_artifact_path) {
@ -365,13 +366,17 @@ export function resolveEscalation(
buildAuditEnvelope({
traceId: `escalation:${milestoneId}:${sliceId}:${taskId}`,
category: "gate",
type: "escalation-user-responded",
type:
source === "auto-mode"
? "escalation-auto-accepted"
: "escalation-user-responded",
payload: {
milestoneId,
sliceId,
taskId,
chosenOptionId: chosenOption?.id,
rationale,
resolvedBy: source,
},
}),
);