fix(cron): require target proof for delivery verifier

This commit is contained in:
Mason Huang
2026-06-13 21:04:21 +08:00
parent 4c23d1d597
commit 2aff537f9f
4 changed files with 75 additions and 8 deletions
@@ -346,6 +346,7 @@ describe("runCronIsolatedAgentTurn message tool policy", () => {
},
messageToolEnabled: true,
messageToolForced: false,
requireExplicitMessageTargetEvidence: true,
directFallback: true,
}),
skillsSnapshot: emptySkillsSnapshot,
@@ -900,14 +901,35 @@ describe("runCronIsolatedAgentTurn message tool policy", () => {
});
});
it("skips cron fallback delivery when the message tool sends to the bound target", async () => {
await expectCronFallbackSkippedForMessageToolDelivery({
sentTargets: [],
job: {
id: "message-tool-bound-target",
name: "Message Tool Bound Target",
it("uses cron fallback delivery when the message tool returns no target evidence", async () => {
mockRunCronFallbackPassthrough();
resolveCronDeliveryPlanMock.mockReturnValue(makeAnnounceDeliveryPlan());
runEmbeddedAgentMock.mockResolvedValue(makeMessageToolRunResult([]));
const result = await runCronIsolatedAgentTurn({
...makeParams(),
job: makeAnnounceMessageToolJob({
id: "message-tool-no-target-evidence",
name: "Message Tool No Target Evidence",
}),
});
expect(dispatchCronDeliveryMock).toHaveBeenCalledTimes(1);
expectDispatchFields({
deliveryRequested: true,
sourceDeliveryOutcome: {
visibleDeliveries: [],
verifiedMessageToolDelivery: false,
satisfiesSourceDelivery: false,
unverifiedMessageToolDelivery: false,
},
});
expectDeliveryFields(result.delivery, {
intended: { channel: "messagechat", to: "123", source: "explicit" },
resolved: { ok: true, channel: "messagechat", to: "123", source: "explicit" },
fallbackUsed: true,
delivered: true,
});
});
it("rewrites generic message provider to resolved channel in delivery trace", async () => {
+1
View File
@@ -335,6 +335,7 @@ function resolveCronSourceDeliveryPlan(params: {
target,
messageToolEnabled: true,
messageToolForced: false,
requireExplicitMessageTargetEvidence: true,
directFallback: true,
skipFallbackWhenMessageToolSentToTarget: params.resolvedDelivery.ok,
});
@@ -145,6 +145,46 @@ describe("source delivery plan", () => {
expect(outcome.unverifiedMessageToolDelivery).toBe(false);
});
it("synthesizes the planned target for legacy message-tool sends by default", () => {
const contract = createSourceDeliveryPlan({
owner: "message_tool_then_direct_fallback",
reason: "cron_announce",
target: { channel: "slack", to: "channel:C1" },
});
const outcome = resolveSourceDeliveryOutcome(contract, {
didSendViaMessageTool: true,
});
expect(outcome.visibleDeliveries).toEqual([
{
via: "message_tool",
verifiedTarget: true,
target: { tool: "message", provider: "slack", to: "channel:C1" },
},
]);
expect(outcome.verifiedMessageToolDelivery).toBe(true);
expect(outcome.satisfiesSourceDelivery).toBe(true);
});
it("does not synthesize the planned target when explicit target evidence is required", () => {
const contract = createSourceDeliveryPlan({
owner: "message_tool_then_direct_fallback",
reason: "cron_announce",
target: { channel: "slack", to: "channel:C1" },
requireExplicitMessageTargetEvidence: true,
});
const outcome = resolveSourceDeliveryOutcome(contract, {
didSendViaMessageTool: true,
});
expect(outcome.visibleDeliveries).toEqual([]);
expect(outcome.verifiedMessageToolDelivery).toBe(false);
expect(outcome.satisfiesSourceDelivery).toBe(false);
expect(outcome.unverifiedMessageToolDelivery).toBe(false);
});
it("does not synthesize an implicit target without a concrete recipient", () => {
const contract = createSourceDeliveryPlan({
owner: "direct_fallback",
+6 -2
View File
@@ -69,6 +69,7 @@ export type SourceDeliveryPlan = {
enabled: boolean;
force: boolean;
requireExplicitTarget: boolean;
requireExplicitTargetEvidence: boolean;
defaultTarget: boolean;
};
fallback: {
@@ -174,6 +175,7 @@ export function createSourceDeliveryPlan(params: {
messageToolEnabled?: boolean;
messageToolForced?: boolean;
requireExplicitMessageTarget?: boolean;
requireExplicitMessageTargetEvidence?: boolean;
directFallback?: boolean;
skipFallbackWhenMessageToolSentToTarget?: boolean;
fallbackBestEffort?: boolean;
@@ -197,6 +199,7 @@ export function createSourceDeliveryPlan(params: {
enabled: params.messageToolEnabled ?? messageToolOwnsDelivery,
force: params.messageToolForced ?? messageToolOwnsDelivery,
requireExplicitTarget: params.requireExplicitMessageTarget ?? false,
requireExplicitTargetEvidence: params.requireExplicitMessageTargetEvidence ?? false,
defaultTarget: Boolean(params.target?.channel || params.target?.to),
},
fallback: {
@@ -239,11 +242,12 @@ export function resolveSourceDeliveryOutcome(
): SourceDeliveryOutcome {
const didSendViaMessageTool = params.didSendViaMessageTool === true;
const explicitTargets = params.messageToolSentTargets ?? [];
// A send without explicit target metadata still counts when the plan has a default target.
// Cron completion accounting needs concrete target evidence. Legacy
// message-tool-owned flows may still use the plan target as the implicit send.
const sentTargets =
explicitTargets.length > 0
? explicitTargets
: didSendViaMessageTool
: didSendViaMessageTool && !plan.messageTool.requireExplicitTargetEvidence
? [resolveImplicitMessageToolDeliveryTarget(plan)].filter(
(target): target is SourceDeliveryMessageToolTarget => Boolean(target),
)