Patron support sync keeps Bifrost's answer by status alone

Soul pass 4, F2: the route stored Bifrost's whole response body in its
audit event and returned it to the caller, as `detail` on a refusal and as
`bifrostResponse` on success. Another service's body can quote the signed
fact or its own configuration.

Both audit events now carry bifrostStatus and no body; a refusal answers
502 with fixed text and Bifrost's status; success answers bifrostStatus in
place of the body, which no caller reads (Bifrost's docs name only the
route). The body is discarded unread.

Tests drive the route against a Bifrost that answers 200 and 403 with the
canary in its body, and pin the reply and the audit event exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-30 17:08:29 +02:00
commit 2e06678301
2 changed files with 54 additions and 11 deletions

View file

@ -1689,7 +1689,10 @@ export async function buildApp(options: BuildAppOptions = {}): Promise<FastifyIn
},
body,
});
const bifrostBody = await bifrostResponse.text();
// Bifrost's answer is kept by its status alone, in the audit event and
// in the reply: another service's body can quote the signed fact or
// its own configuration, and both of those are stored and relayed.
await bifrostResponse.body?.cancel();
await store.createAuditEvent({
accountId: request.body.accountId,
@ -1700,7 +1703,6 @@ export async function buildApp(options: BuildAppOptions = {}): Promise<FastifyIn
requiredTierTitle: request.body.requiredTierTitle,
providerEventId: supportFact.providerEventId,
bifrostStatus: bifrostResponse.status,
bifrostResponse: bifrostBody,
},
createdAt: now,
});
@ -1710,14 +1712,14 @@ export async function buildApp(options: BuildAppOptions = {}): Promise<FastifyIn
return {
error: "bifrost_patron_support_rejected",
statusCode: bifrostResponse.status,
detail: bifrostBody,
detail: "Bifrost refused the patron support fact.",
};
}
return {
status: "synced",
supportFact,
bifrostResponse: bifrostBody,
bifrostStatus: bifrostResponse.status,
};
}
);

View file

@ -518,7 +518,7 @@ describe("Heimdall service", () => {
}
});
it("syncs a linked Patreon membership into a signed Bifrost support fact", async () => {
async function syncPatronSupport(bifrostAnswer: () => Response) {
const baseConfig = createTestConfig();
const config = {
...baseConfig,
@ -589,7 +589,7 @@ describe("Heimdall service", () => {
bifrostBody = String(init?.body ?? "");
const headers = init?.headers as Record<string, string>;
bifrostSignature = headers["x-heimdall-signature-256"] ?? "";
return new Response("processed", { status: 200 });
return bifrostAnswer();
}
return new Response("unexpected request", { status: 500 });
@ -608,6 +608,15 @@ describe("Heimdall service", () => {
},
});
const auditEvents = [...(store as unknown as { auditEvents: Map<string, { eventType: string; eventPayloadJson: unknown }> }).auditEvents.values()];
return { response, auditEvents, bifrostBody, bifrostSignature, now };
}
it("syncs a linked Patreon membership into a signed Bifrost support fact", async () => {
const { response, auditEvents, bifrostBody, bifrostSignature, now } = await syncPatronSupport(
() => new Response("processed CANARYbifrost", { status: 200 })
);
expect(response.statusCode, response.body).toBe(200);
expect(bifrostSignature).toMatch(/^sha256=[0-9a-f]{64}$/);
expect(JSON.parse(bifrostBody)).toEqual({
@ -624,12 +633,44 @@ describe("Heimdall service", () => {
providerSubscriptionId: "member-789",
notes: "Verified active Patreon membership for tier Inner Sanctum.",
});
expect(response.json()).toEqual(
expect.objectContaining({
status: "synced",
bifrostResponse: "processed",
})
expect(response.json()).toEqual(expect.objectContaining({ status: "synced", bifrostStatus: 200 }));
expect(auditEvents.map((event) => [event.eventType, event.eventPayloadJson])).toEqual([
[
"bifrost_patron_support_synced",
{
provider: "patreon",
requiredTierTitle: "Inner Sanctum",
providerEventId: "patreon-membership-snapshot:patreon-user-456:member-789:2026-06-09:1500",
bifrostStatus: 200,
},
],
]);
expect(response.body + JSON.stringify(auditEvents)).not.toContain("CANARY");
});
it("answers a Bifrost refusal with fixed text and its status, never its body", async () => {
const { response, auditEvents } = await syncPatronSupport(
() => new Response('{"error":"bad signature","echo":"CANARYrefusal"}', { status: 403 })
);
expect(response.statusCode).toBe(502);
expect(response.json()).toEqual({
error: "bifrost_patron_support_rejected",
statusCode: 403,
detail: "Bifrost refused the patron support fact.",
});
expect(auditEvents.map((event) => [event.eventType, event.eventPayloadJson])).toEqual([
[
"bifrost_patron_support_sync_failed",
{
provider: "patreon",
requiredTierTitle: "Inner Sanctum",
providerEventId: "patreon-membership-snapshot:patreon-user-456:member-789:2026-06-09:1500",
bifrostStatus: 403,
},
],
]);
expect(response.body + JSON.stringify(auditEvents)).not.toContain("CANARY");
});
it("redeems a completion code exactly once", async () => {