Files
fusion/packages/engine/src/goal-injection-diagnostics.ts
gsxdsm 866d01f532 FN-5959: bridge task goal provenance from mission links
Bridge mission-linked task provenance into goal diagnostics.

- derive task goal ids and goal records from the linked mission hierarchy with a task mission fallback
- record provenanceGoalIds in goal-injection diagnostics, run-audit metadata, and executor task logs
- add regression coverage and mission/diagnostics docs for derived task-to-goal provenance

Files changed:
 docs/diagnostics.md                                |   5 +-
 docs/missions.md                                   |  19 ++++
 packages/core/src/__tests__/mission-store.test.ts  | 123 +++++++++++++++++++++
 packages/core/src/mission-store.ts                 |  55 ++++++++-
 packages/engine/src/__tests__/goal-injection-diagnostics-wiring.test.ts      |  35 +++++-
 packages/engine/src/goal-injection-diagnostics.ts  |  21 +++-
 6 files changed, 247 insertions(+), 11 deletions(-)

Fusion-Task-Id: FN-5959

Fusion-Task-Lineage: 7c76ae98-37b1-421c-b477-a0147d034a4c
2026-06-03 15:32:29 -07:00

268 lines
8.9 KiB
TypeScript

import type { Goal } from "@fusion/core";
import { buildGoalContextSection, type GoalInjectionResult } from "./goal-context-injector.js";
import type { TaskStore } from "@fusion/core";
import type { GoalAnchoringLane } from "./goal-anchoring-audit.js";
import { emitGoalAnchoringAudit } from "./goal-anchoring-audit.js";
import type { EngineRunContext } from "./run-audit.js";
import type { RunAuditor } from "./run-audit.js";
import { createLogger } from "./logger.js";
const diagnosticsLog = createLogger("goal-injection-diagnostics");
export type GoalInjectionOutcome = "applied" | "no-goals" | "disabled-or-failed";
export type GoalInjectionDisabledReason =
| "config-disabled"
| "store-unavailable"
| "list-failed"
| "injector-threw";
export interface GoalInjectionDiagnostic {
lane: GoalAnchoringLane;
outcome: GoalInjectionOutcome;
goalCount: number;
goalIds: string[];
provenanceGoalIds: string[];
truncated: boolean;
reason?: GoalInjectionDisabledReason;
errorClass?: string;
runId?: string;
agentId?: string;
taskId?: string;
timestamp: string;
}
export interface GoalInjectionDiagnosticInput extends Omit<GoalInjectionDiagnostic, "timestamp" | "provenanceGoalIds"> {
provenanceGoalIds?: string[];
store?: TaskStore;
runContext?: EngineRunContext | null;
}
export interface GoalInjectionClassification {
outcome: GoalInjectionOutcome;
goalCount: number;
goalIds: string[];
truncated: boolean;
reason?: GoalInjectionDisabledReason;
errorClass?: string;
}
export function classifyGoalInjectionResult(result: GoalInjectionResult): GoalInjectionClassification {
if (result.emittedGoalIds.length === 0) {
return {
outcome: "no-goals",
goalCount: 0,
goalIds: [],
truncated: false,
};
}
return {
outcome: "applied",
goalCount: result.emittedGoalIds.length,
goalIds: result.emittedGoalIds,
truncated: result.truncated !== null,
};
}
export function classifyGoalInjectionFailure(
reason: GoalInjectionDisabledReason,
error?: unknown,
): GoalInjectionClassification {
const resolvedErrorClass =
error && typeof error === "object" && "constructor" in error && typeof (error as { constructor?: { name?: unknown } }).constructor?.name === "string"
? (error as { constructor: { name: string } }).constructor.name
: undefined;
return {
outcome: "disabled-or-failed",
goalCount: 0,
goalIds: [],
truncated: false,
reason,
...(resolvedErrorClass ? { errorClass: resolvedErrorClass } : {}),
};
}
export interface GoalContextResolution {
goalContext: string;
classification: GoalInjectionClassification;
}
export interface ResolveAndEmitGoalContextInput {
lane: GoalAnchoringLane;
store: TaskStore;
audit: RunAuditor;
taskId?: string;
runContext?: EngineRunContext | null;
}
export interface ResolveGoalContextInput {
listActiveGoals?: () => Goal[];
injector?: (activeGoals: Goal[]) => GoalInjectionResult;
disabledReason?: GoalInjectionDisabledReason;
}
export function resolveGoalContextForDiagnostics(input: ResolveGoalContextInput): GoalContextResolution {
if (!input.listActiveGoals) {
return {
goalContext: "",
classification: classifyGoalInjectionFailure(input.disabledReason ?? "store-unavailable"),
};
}
try {
const activeGoals = input.listActiveGoals();
const injector = input.injector ?? ((goals: Goal[]) => buildGoalContextSection({ activeGoals: goals }));
try {
const injectionResult = injector(activeGoals);
return {
goalContext: injectionResult.text,
classification: classifyGoalInjectionResult(injectionResult),
};
} catch (injectorError) {
return {
goalContext: "",
classification: classifyGoalInjectionFailure("injector-threw", injectorError),
};
}
} catch (listError) {
return {
goalContext: "",
classification: classifyGoalInjectionFailure("list-failed", listError),
};
}
}
export async function resolveAndEmitGoalContext(input: ResolveAndEmitGoalContextInput): Promise<GoalContextResolution> {
const resolution = resolveGoalContextForDiagnostics({
listActiveGoals:
typeof input.store.getGoalStore === "function"
? () => input.store.getGoalStore().listGoals({ status: "active" })
: undefined,
});
let provenanceGoalIds: string[] = [];
if (input.taskId && typeof input.store.getMissionStore === "function") {
try {
provenanceGoalIds = input.store.getMissionStore().listGoalIdsForTask(input.taskId);
} catch (error) {
diagnosticsLog.warn(
`failed to resolve goal provenance for task ${input.taskId} in ${input.lane}: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
await emitGoalAnchoringAudit(input.audit, {
lane: input.lane,
taskId: input.taskId,
goalsInjected: resolution.classification.goalCount,
goalIds: resolution.classification.goalIds,
truncated: resolution.classification.truncated,
reason: resolution.classification.outcome === "no-goals" ? "no-active-goals" : undefined,
});
await emitGoalInjectionDiagnostic({
lane: input.lane,
...resolution.classification,
provenanceGoalIds,
runId: input.runContext?.runId,
agentId: input.runContext?.agentId,
taskId: input.taskId,
store: input.store,
runContext: input.runContext,
});
return resolution;
}
function formatAgentLogLine(input: GoalInjectionDiagnostic): string {
const ids = JSON.stringify(input.goalIds);
const provenanceIds = JSON.stringify(input.provenanceGoalIds);
const reason = input.reason ? ` reason=${input.reason}` : "";
const errorClass = input.errorClass ? ` err=${input.errorClass}` : "";
return `[goal-injection] ${input.outcome} count=${input.goalCount} ids=${ids} provenance=${provenanceIds} truncated=${String(input.truncated)}${reason}${errorClass}`;
}
/**
* Emit per-run goal-context injection diagnostics for executor/heartbeat lanes.
*
* Outcomes:
* - `applied`: at least one active goal ID was injected.
* - `no-goals`: injector executed successfully but active goal set was empty.
* - `disabled-or-failed`: injection was disabled or list/injector execution failed.
*
* Guardrail: this emitter stores goal IDs/counts only and must never persist
* prompt body text, goal titles, or goal descriptions to avoid prompt/PII leakage.
*
* This helper is called from FN-5653 wiring sites (executor/heartbeat), not from
* inside the pure goal-context injector module.
*/
export async function emitGoalInjectionDiagnostic(
input: GoalInjectionDiagnosticInput,
): Promise<GoalInjectionDiagnostic> {
const record: GoalInjectionDiagnostic = {
lane: input.lane,
outcome: input.outcome,
goalCount: input.goalCount,
goalIds: [...input.goalIds],
provenanceGoalIds: [...(input.provenanceGoalIds ?? [])],
truncated: input.truncated,
...(input.reason ? { reason: input.reason } : {}),
...(input.errorClass ? { errorClass: input.errorClass } : {}),
...(input.runId ? { runId: input.runId } : {}),
...(input.agentId ? { agentId: input.agentId } : {}),
...(input.taskId ? { taskId: input.taskId } : {}),
timestamp: new Date().toISOString(),
};
if (input.store && record.taskId) {
try {
await input.store.logEntry(record.taskId, formatAgentLogLine(record), undefined, input.runContext ?? undefined);
} catch (error) {
diagnosticsLog.warn(
`failed to append goal-injection task log for ${record.taskId}: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
const auditStore = input.store;
const hasAuditWriter = Boolean(auditStore && typeof auditStore.recordRunAuditEvent === "function");
if (!input.runContext || !hasAuditWriter || !auditStore) {
diagnosticsLog.warn(
`goal-injection diagnostic emitted without run-audit side effect (lane=${record.lane}, hasContext=${Boolean(input.runContext)}, hasWriter=${Boolean(hasAuditWriter)})`,
);
return record;
}
try {
await auditStore.recordRunAuditEvent({
taskId: input.runContext.taskId,
agentId: input.runContext.agentId,
runId: input.runContext.runId,
domain: "database",
mutationType: "prompt:goal-injection",
target: record.lane,
metadata: {
lane: record.lane,
outcome: record.outcome,
goalCount: record.goalCount,
goalIds: record.goalIds,
provenanceGoalIds: record.provenanceGoalIds,
truncated: record.truncated,
...(record.reason ? { reason: record.reason } : {}),
...(record.errorClass ? { errorClass: record.errorClass } : {}),
...(record.runId ? { runId: record.runId } : {}),
...(record.agentId ? { agentId: record.agentId } : {}),
...(record.taskId ? { taskId: record.taskId } : {}),
},
});
} catch (error) {
diagnosticsLog.warn(
`failed to append goal-injection run-audit event for lane=${record.lane}: ${error instanceof Error ? error.message : String(error)}`,
);
}
return record;
}