Files
PaperClipAI/server/src/__tests__/fast-responses.test.ts
T
DottaandPaperclip ba5bba86dc fix(server): wake GitHub acknowledgements from committed changes (#17656)
<!-- entire-trail-link-start -->
https://entire.com/gh/paperclipai/-/trails/206/changes/paperclip/206
<!-- entire-trail-link-end -->

## Thinking Path

> - Paperclip coordinates agent work and presents its progress in chat.
> - GitHub deliveries save acknowledgement actions and provider
receipts.
> - Periodic maintenance currently searches for unfinished
acknowledgements even when idle.
> - Committed delivery, lease, response and presentation changes
identify when work can advance.
> - The existing owner should wake on those changes and recover only at
saved deadlines.
> - Completed history should become quiet without losing unfinished
provider effects.

## Linked Issues or Issue Description

Refs #17653, which is merged. This existing GitHub acknowledgement slice
targets master.

**Problem:** A saved GitHub acknowledgement can wait for a publication
lease, fast-response result or native presentation decision. Polling
empty or unchanged work keeps the tenant active. Removing maintenance
without covering these producers can strand the acknowledgement.

**Expected behavior:** Committed changes wake the scoped owner. It
reconstructs existing actions on startup, retries at saved deadlines,
parks unchanged presentation waits and becomes quiet when settled.

## What Changed

- Register GitHub acknowledgement work with the shared scoped deadline
coordinator. Reuse existing action, delivery, lease and fast-response
rows.
- Add transaction-aware response-availability notifications at supported
writes. Keep GitHub protocol details in its platform owner and shared
notifications limited to IDs.
- Share the bounded latest-request/run projection between scheduling and
direct response authorization. An absent native presentation decision
parks the action until its real writer commits.
- Retain publication leases, source/principal authority, version
fencing, response markers and final receipt precedence. Check admission
before each new provider request; let in-flight work settle. Stop does
not consume the provider retry budget.
- Remove only recurring acknowledgement recovery and keep its manual
path. Add no work table or scheduler service.
- Regenerate the three original indexes as
`0400_absent_phantom_reporter.sql`: pending acknowledgement IDs,
fast-response delivery lookup and publication comment/destination
lookup. Each uses concurrent/idempotent creation and the existing
top-level migration runner. Preserve all landed SQL/journal entries and
the five newest snapshots.

## Verification

Published head: `09ca00b9f029838434ec042cda691345d67b493e`, on landed
continuation parent `063cfd43813cabf18417605fb9eefb9aacf95c8d`.

- **87 distinct current cases pass:** 18 real PostgreSQL acknowledgement
lifecycle/presentation cases, all 26 fast-response service cases, all 30
heartbeat completion cases and all 13 startup cases. Database build,
server typechecking/build, module boundaries and three fresh migrations
through 0400 pass. The current Runner bundle rebuild also passes after
upstream provider-event changes.
- The lifecycle matrix covers empty/history quiet, lease release,
restart, drain/standby, fast-response failure/expiry/publication/unknown
outcome, streaming quarantine, outer rollback/commit, saved provider
retry, stop between requests and receipt state. The real presentation
writer proves quiet while pending, rollback, committed reactivation and
final quiet.
- The earlier **305-case** matrix (236 full GitHub workflow cases, 26
fast-response service cases, 30 completion cases and 13 startup cases)
passes at `337f272853d4f8a51b5b9300900a46eb4e13f698`. It retains its
prepared-parent scope. Repeated cases are not added to current 87.
- A populated 0399 database upgrades to 0400 with all 398 prior
migration receipts unchanged, one new receipt and no additional receipt
on replay. All three indexes are valid/ready on fresh and upgraded
databases. Their SQL definitions match the original draft after
whitespace normalization; migration history and retained snapshot bytes
are preserved.
- Normal integration `9b3607e159153e00ccdfe538d52b591bd38d6499`
preserves the full 28-path owned binary patch and all 20 unrelated
master paths, including native retry, provider-event and evaluation
workflow changes. Both normal merge parents and the exact tree are
checked. No force push or rebase occurs.
- [The owner
audit](https://github.com/paperclipai/paperclip/blob/09ca00b9f029838434ec042cda691345d67b493e/doc/plans/2026-10-25-github-chat-maintenance-sleep-audit.md#acceptance-on-the-actual-landed-continuation-parent)
retains the original missing-notification, JSON precedence, receipt
lifecycle, external-database fixture and migration-registry failures.
Earlier 69/228/broad counts keep their original scope. Skips and failed
revisions are not relabelled as current passes.
- The final audit-only edit leaves all owned source/test/migration
hashes unchanged. **Fresh exact-head CI and Greptile 5/5 with no
actionable findings remain required.** No full local repository, live
GitHub tenant or deployed sleep result is claimed.

## Risks

- Process-local transaction hints cover supported application writers
and shared pools. Independent SQL writers and other replicas do not gain
a new wake guarantee.
- Provider effects remain guarded by the existing publication lease and
current source/principal authority. Unknown outcomes retain their
existing recovery semantics.
- Presentation waits depend on their supported milestone/transaction
producers. A missing producer can defer recovery until another supported
change or restart.
- Index creation can take time on large histories and runs concurrently
outside a transaction. Existing migration recovery handles replay; prior
SQL is unchanged.
- Worker quiet is distinct from host/database suspension. Cloud wake
coverage and sleep admission remain unchanged; later maintenance duties
are separate slices.

## Model Used

OpenAI Codex, using reasoning, repository inspection, code execution and
real PostgreSQL tests. The exact model identifier and context-window
size are not exposed in this session.

## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-28 16:06:42 -05:00

664 lines
23 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { randomUUID } from "node:crypto";
import { mkdtemp, realpath, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { and, eq } from "drizzle-orm";
import {
createDb,
companies,
agents,
companyMemberships,
companyFastResponses,
costEvents,
fastResponseRequests,
connectionGrants,
connectionGrantMembers,
budgetPolicies,
budgetReservations,
heartbeatRunEvents,
heartbeatRuns,
issues,
issueComments,
} from "@paperclipai/db";
import { startEmbeddedPostgresTestDatabase } from "@paperclipai/db/test-embedded-postgres";
import { aiConnectionService } from "../services/ai-connections.js";
import {
enqueueFastResponse,
supersedeFastResponse,
fastResponseTurnQueued,
fastResponseService,
} from "../services/fast-responses.js";
import {
fastResponseReceipt,
type FastResponseOutcome,
} from "../services/fast-response-provider.js";
import { instanceSettingsService } from "../services/instance-settings.js";
import { settleConversationTurn } from "../services/agent-conversations.js";
import { buildLowTrustSourceTrust, LOW_TRUST_QUARANTINED_BODY } from "../services/source-trust.js";
import { deliverBudgetEnforcement } from "../services/budgets.js";
let database: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>> | undefined,
db: ReturnType<typeof createDb>,
home: string;
beforeAll(async () => {
home = await realpath(
await mkdtemp(path.join(os.tmpdir(), "paperclip-fast-response-")),
);
vi.stubEnv("PAPERCLIP_HOME", home);
vi.stubEnv("PAPERCLIP_INSTANCE_ID", "fast-response-fixture");
const external = process.env.PAPERCLIP_TEST_DATABASE_URL;
if (external && !new URL(external).pathname.endsWith("_test")) throw new Error("Fast-response tests require a database ending in _test");
database = external ? undefined : await startEmbeddedPostgresTestDatabase(
"paperclip-fast-response-db-",
);
db = createDb(external ?? database!.connectionString);
}, 90000);
afterAll(async () => {
await db?.$client.end({ timeout: 1 });
await database?.cleanup();
vi.unstubAllEnvs();
if (home) await rm(home, { recursive: true, force: true });
});
const outcome: FastResponseOutcome = {
text: "I’ll check the settings panel’s border styling and work on the fix.",
receipt: fastResponseReceipt({
usage: { inputTokens: 80, outputTokens: 16 },
response: { id: "response-1", body: { usage: { cost: 0.00001 } } },
}),
};
let sequence = 0;
async function fixture() {
await instanceSettingsService(db).updateExperimental({ enableFastResponses: true });
const companyId = randomUUID(),
agentId = randomUUID(),
issueId = randomUUID();
await db.insert(companies).values({
id: companyId,
name: "Fast response tests",
issuePrefix: `FR${++sequence}`,
});
await db.insert(agents).values({
id: agentId,
companyId,
name: "Alex",
adapterType: "codex_local",
});
await db.insert(companyMemberships).values(
["alice", "bob"].map((principalId) => ({
companyId,
principalId,
principalType: "user",
status: "active",
membershipRole: "owner",
})),
);
const binding = await aiConnectionService(db).save(
companyId,
"alice",
{
provider: "openrouter",
method: "api_key",
name: "Fast key",
ownership: "shared",
apiKey: "fixture-key",
agentIds: [],
allAgents: true,
},
"fixture-key",
);
const provider = vi.fn().mockResolvedValue(structuredClone(outcome)),
service = fastResponseService(db, { provider });
const config = {
connectionId: binding.connectionId,
grantId: binding.grantId,
enabled: true,
model: "openai/gpt-oss-120b",
allowSponsored: true,
};
await service.configure(companyId, "alice", config);
await db.insert(issues).values({
id: issueId,
companyId,
title: "Settings panel",
status: "in_progress",
assigneeAgentId: agentId,
});
const [comment] = await db
.insert(issueComments)
.values({
companyId,
issueId,
authorUserId: "alice",
body: "Fix the border styling",
})
.returning();
const source = {
companyId,
agentId,
issueId,
responsibleUserId: "alice",
sourceCommentId: comment.id,
sourceKey: `comment:${comment.id}`,
acceptedAt: comment.createdAt,
};
const enqueue = async () => {
await db.transaction((tx) =>
enqueueFastResponse(tx as unknown as typeof db, source),
);
return (
await db
.select()
.from(fastResponseRequests)
.where(eq(fastResponseRequests.companyId, companyId))
)[0];
};
return {
companyId,
agentId,
issueId,
comment,
source,
config,
binding,
provider,
service,
enqueue,
};
}
describe("fast response accepted turns", () => {
it("returns acknowledgement text and a committed charge before slow budget stop delivery", async () => {
const f = await fixture();
await db.insert(budgetPolicies).values({ companyId: f.companyId, scopeType: "company", scopeId: f.companyId,
windowKind: "calendar_month_utc", amount: 1, notifyEnabled: false });
f.provider.mockResolvedValue({ ...structuredClone(outcome), receipt: { ...outcome.receipt, costCents: "2.0000000" } });
let release!: () => void;
const gate = new Promise<void>(resolve => { release = resolve; });
const cancelWorkForScope = vi.fn(() => gate);
const service = fastResponseService(db, { provider: f.provider, budgetHooks: { cancelWorkForScope } });
const pending = service.test(f.companyId, "alice");
let delivery: Promise<void> | undefined;
let finished = false;
void pending.then(() => { finished = true; }, () => { finished = true; });
try {
await vi.waitFor(() => expect(finished).toBe(true), { timeout: 3000 });
expect(await pending).toMatchObject({ status: "succeeded", text: outcome.text });
expect(f.provider).toHaveBeenCalledOnce();
expect(cancelWorkForScope).not.toHaveBeenCalled();
const events = await db.select().from(costEvents).where(eq(costEvents.companyId, f.companyId));
expect(events).toHaveLength(1);
expect(events[0]).toMatchObject({ usageKind: "fast_response", costCents: 2 });
const [policy] = await db.select().from(budgetPolicies).where(eq(budgetPolicies.companyId, f.companyId));
expect(policy.enforcementVersion).toBeGreaterThan(policy.enforcementDeliveredVersion);
delivery = deliverBudgetEnforcement(db, { cancelWorkForScope }, f.companyId);
await vi.waitFor(() => expect(cancelWorkForScope).toHaveBeenCalledOnce());
expect((await db.select().from(budgetPolicies).where(eq(budgetPolicies.id, policy.id)))[0].enforcementDeliveredVersion).toBe(policy.enforcementDeliveredVersion);
release(); await delivery;
expect((await db.select().from(budgetPolicies).where(eq(budgetPolicies.id, policy.id)))[0].enforcementDeliveredVersion).toBe(policy.enforcementVersion);
} finally { release(); await pending.catch(() => undefined); await delivery?.catch(() => undefined); }
});
it("does not enqueue or infer while the experimental setting is disabled", async () => {
const f = await fixture();
await instanceSettingsService(db).updateExperimental({ enableFastResponses: false });
expect(await f.enqueue()).toBeUndefined();
expect(await f.service.availability(f.companyId, "alice")).toMatchObject({ available: false });
expect(await f.service.test(f.companyId, "alice")).toMatchObject({ status: "unavailable", reason: "disabled" });
expect(f.provider).not.toHaveBeenCalled();
});
it("suppresses pending work when the experimental setting is switched off", async () => {
const f = await fixture();
const request = await f.enqueue();
await instanceSettingsService(db).updateExperimental({ enableFastResponses: false });
await f.service.process(request);
expect(f.provider).not.toHaveBeenCalled();
const [row] = await db.select().from(fastResponseRequests).where(eq(fastResponseRequests.id, request.id));
expect(row).toMatchObject({ status: "skipped", publicationStatus: "suppressed", commentId: null });
});
it("retains charges but suppresses a response when disabled during inference", async () => {
const f = await fixture();
const request = await f.enqueue();
f.provider.mockImplementationOnce(async () => {
await instanceSettingsService(db).updateExperimental({ enableFastResponses: false });
return structuredClone(outcome);
});
await f.service.process(request);
const [row] = await db.select().from(fastResponseRequests).where(eq(fastResponseRequests.id, request.id));
expect(row).toMatchObject({ status: "succeeded", publicationStatus: "suppressed", commentId: null });
expect(row.costEventId).toBeTruthy();
});
it.each(["recent", "source"] as const)("keeps quarantined %s text out of the acknowledgement prompt", async location => {
const f = await fixture();
const untrusted = "QUARANTINED_PRIVATE_FINDING_DO_NOT_PROMOTE";
const sourceTrust = buildLowTrustSourceTrust({ issueId: f.issueId, agentId: f.agentId });
if (location === "source") {
await db.update(issueComments).set({ body: untrusted, sourceTrust }).where(eq(issueComments.id, f.comment.id));
} else {
await db.insert(issueComments).values({ companyId: f.companyId, issueId: f.issueId, authorAgentId: f.agentId,
body: untrusted, sourceTrust, createdAt: new Date(f.source.acceptedAt.getTime() - 1) });
}
await f.service.process(await f.enqueue());
expect(f.provider).toHaveBeenCalledTimes(1);
const prompt = f.provider.mock.calls[0][0].prompt as string;
expect(prompt).not.toContain(untrusted);
expect(prompt).toContain(LOW_TRUST_QUARANTINED_BODY);
const [receipt] = await db.select().from(issueComments).where(and(eq(issueComments.issueId, f.issueId), eq(issueComments.origin, "fast_response")));
expect(receipt.body).not.toContain(untrusted);
expect(receipt.sourceTrust).toEqual(location === "source" ? sourceTrust : null);
});
it("distinguishes the current turn's running agent from older or queued work", async () => {
const f = await fixture();
expect(await fastResponseTurnQueued(db, f.source)).toBe(false);
const [run] = await db
.insert(heartbeatRuns)
.values({
companyId: f.companyId,
agentId: f.agentId,
status: "running",
contextSnapshot: { issueId: f.issueId, wakeCommentId: f.comment.id },
createdAt: new Date(f.source.acceptedAt.getTime() - 1),
})
.returning();
expect(await fastResponseTurnQueued(db, f.source)).toBe(false);
await db
.update(heartbeatRuns)
.set({
contextSnapshot: { issueId: f.issueId, wakeCommentId: randomUUID() },
})
.where(eq(heartbeatRuns.id, run.id));
expect(await fastResponseTurnQueued(db, f.source)).toBe(true);
await db
.update(heartbeatRuns)
.set({
status: "queued",
createdAt: new Date(f.source.acceptedAt.getTime() + 1),
})
.where(eq(heartbeatRuns.id, run.id));
expect(await fastResponseTurnQueued(db, f.source)).toBe(true);
});
it("starts disabled, and disabled turns have no request or cost", async () => {
const f = await fixture();
await db
.delete(companyFastResponses)
.where(eq(companyFastResponses.companyId, f.companyId));
expect(await f.service.settings(f.companyId)).toMatchObject({
enabled: false,
allowSponsored: true,
});
expect(await f.enqueue()).toBeUndefined();
expect(f.provider).not.toHaveBeenCalled();
});
it("deduplicates concurrent workers and persists provenance without fabricating a run", async () => {
const f = await fixture();
const row = await f.enqueue();
await f.enqueue();
await Promise.all([f.service.process(row), f.service.process(row)]);
expect(f.provider).toHaveBeenCalledTimes(1);
const receipts = await db
.select()
.from(issueComments)
.where(
and(
eq(issueComments.companyId, f.companyId),
eq(issueComments.origin, "fast_response"),
),
);
expect(receipts).toHaveLength(1);
expect(receipts[0]).toMatchObject({
authorAgentId: f.agentId,
createdByRunId: null,
fastResponseRequestId: row.id,
});
expect(
await db
.select()
.from(heartbeatRuns)
.where(eq(heartbeatRuns.companyId, f.companyId)),
).toHaveLength(0);
await db
.update(issues)
.set({
conversationAgentId: f.agentId,
conversationUserId: "alice",
conversationState: "active",
})
.where(eq(issues.id, f.issueId));
const [run] = await db
.insert(heartbeatRuns)
.values({
companyId: f.companyId,
agentId: f.agentId,
status: "succeeded",
invocationSource: "on_demand",
contextSnapshot: { issueId: f.issueId, wakeCommentId: f.comment.id },
})
.returning();
expect(await settleConversationTurn(db, run)).toBe(false);
const [event] = await db
.select()
.from(costEvents)
.where(eq(costEvents.companyId, f.companyId));
expect(event).toMatchObject({
usageKind: "fast_response",
heartbeatRunId: null,
costCents: 0.001,
responsibleUserId: "alice",
});
expect(
(await db.select().from(issues).where(eq(issues.id, f.issueId)))[0]
.status,
).toBe("in_progress");
expect(
(
await db
.select()
.from(budgetReservations)
.where(eq(budgetReservations.fastResponseRequestId, row.id))
)[0].state,
).toBe("settled");
});
it.each([
"reply",
"cancel",
"delete",
"reassign",
"edit",
"revoke",
"reset",
"stream",
"board_stream",
])(
"suppresses output after %s but retains its actual cost",
async (change) => {
const f = await fixture(),
row = await f.enqueue();
if (change === "reset") {
await db
.update(fastResponseRequests)
.set({ sessionGeneration: 0 })
.where(eq(fastResponseRequests.id, row.id));
row.sessionGeneration = 0;
}
f.provider.mockImplementationOnce(async () => {
if (change === "reset")
await db
.update(issues)
.set({ conversationSessionGeneration: 1 })
.where(eq(issues.id, f.issueId));
if (change === "board_stream")
await supersedeFastResponse(db, f.companyId, f.comment.id);
if (change === "stream") {
const [run] = await db
.insert(heartbeatRuns)
.values({
companyId: f.companyId,
agentId: f.agentId,
status: "running",
invocationSource: "on_demand",
contextSnapshot: {
issueId: f.issueId,
wakeCommentId: f.comment.id,
},
})
.returning();
await db.insert(heartbeatRunEvents).values({
companyId: f.companyId,
agentId: f.agentId,
runId: run.id,
seq: 1,
eventType: "item.delta",
payload: {
prpEvent: {
payload: {
kind: "agentMessage",
text: "Here is the recommendation",
},
},
},
});
}
if (change === "reply")
await db.insert(issueComments).values({
companyId: f.companyId,
issueId: f.issueId,
authorAgentId: f.agentId,
body: "I fixed the border.",
});
if (change === "cancel")
await db
.update(issues)
.set({ status: "cancelled" })
.where(eq(issues.id, f.issueId));
if (change === "delete")
await db.delete(issues).where(eq(issues.id, f.issueId));
if (change === "reassign")
await db
.update(issues)
.set({ assigneeAgentId: null })
.where(eq(issues.id, f.issueId));
if (change === "edit")
await db
.update(issueComments)
.set({ body: "Never mind", updatedAt: new Date(Date.now() + 100) })
.where(eq(issueComments.id, f.comment.id));
if (change === "revoke")
await db
.update(connectionGrants)
.set({ status: "revoked" })
.where(eq(connectionGrants.id, f.binding.grantId));
return structuredClone(outcome);
});
await f.service.process(row);
const [result] = await db
.select()
.from(fastResponseRequests)
.where(eq(fastResponseRequests.id, row.id));
expect(result).toMatchObject({
status: "succeeded",
publicationStatus: "suppressed",
commentId: null,
});
expect(
await db
.select()
.from(costEvents)
.where(eq(costEvents.companyId, f.companyId)),
).toHaveLength(1);
},
);
it("does not call a provider for expired or denied linked-user turns", async () => {
const f = await fixture(),
row = await f.enqueue();
await db.insert(connectionGrantMembers).values({
companyId: f.companyId,
grantId: f.binding.grantId,
subjectType: "user",
subjectId: "bob",
});
expect(await f.service.process(row)).toMatchObject({
status: "unavailable",
});
expect(f.provider).not.toHaveBeenCalled();
const g = await fixture(),
expired = await g.enqueue();
await g.service.process({ ...expired, expiresAt: new Date(0) });
expect(g.provider).not.toHaveBeenCalled();
});
it("never regenerates interrupted attempts and keeps unknown charges reserved", async () => {
const f = await fixture(),
row = await f.enqueue();
f.provider.mockResolvedValueOnce({
errorCode: "timeout",
receipt: fastResponseReceipt(),
});
await f.service.process(row);
await f.service.sweepPending();
expect(f.provider).toHaveBeenCalledTimes(1);
expect(
(
await db
.select()
.from(fastResponseRequests)
.where(eq(fastResponseRequests.id, row.id))
)[0].status,
).toBe("unknown");
expect(
(
await db
.select()
.from(budgetReservations)
.where(eq(budgetReservations.fastResponseRequestId, row.id))
)[0].state,
).toBe("held");
});
it("sponsors accepted unlinked senders using only destination-authorized context", async () => {
const f = await fixture();
await db
.update(issues)
.set({ title: "PRIVATE INTERNAL TITLE" })
.where(eq(issues.id, f.issueId));
const external = {
...f.source,
responsibleUserId: null,
sponsored: true,
endpointId: randomUUID(),
};
await db.transaction((tx) =>
enqueueFastResponse(tx as unknown as typeof db, external),
);
const [row] = await db
.select()
.from(fastResponseRequests)
.where(eq(fastResponseRequests.companyId, f.companyId));
const authorizeExternal = vi.fn().mockResolvedValue({
agentName: "Alex",
message: "Review this border",
queued: true,
});
const service = fastResponseService(db, {
provider: f.provider,
authorizeExternal,
});
await service.process(row);
expect(authorizeExternal).toHaveBeenCalled();
expect(f.provider).toHaveBeenCalledTimes(1);
const prompt = f.provider.mock.calls[0][0].prompt;
expect(prompt).toContain("Review this border");
expect(prompt).toContain('"state":"queued"');
expect(prompt).not.toContain("PRIVATE INTERNAL TITLE");
expect(
(
await db
.select()
.from(costEvents)
.where(eq(costEvents.companyId, f.companyId))
)[0],
).toMatchObject({ responsibleUserId: null, usageKind: "fast_response" });
});
it("never treats a denied linked user as company sponsored", async () => {
const f = await fixture();
await db.transaction((tx) =>
enqueueFastResponse(tx as unknown as typeof db, {
...f.source,
sponsored: true,
endpointId: randomUUID(),
}),
);
const [row] = await db
.select()
.from(fastResponseRequests)
.where(eq(fastResponseRequests.companyId, f.companyId));
await f.service.process(row);
expect(f.provider).not.toHaveBeenCalled();
});
it("expires unstarted backlog without an inference attempt", async () => {
const f = await fixture(),
row = await f.enqueue();
await db
.update(fastResponseRequests)
.set({ expiresAt: new Date(0) })
.where(eq(fastResponseRequests.id, row.id));
await f.service.sweepPending();
expect(f.provider).not.toHaveBeenCalled();
expect(
(
await db
.select()
.from(fastResponseRequests)
.where(eq(fastResponseRequests.id, row.id))
)[0],
).toMatchObject({ status: "skipped", errorCode: "expired" });
});
it("honors the company reservation budget before dispatch", async () => {
const f = await fixture();
await db
.insert(budgetPolicies)
.values({
companyId: f.companyId,
scopeType: "company",
scopeId: f.companyId,
windowKind: "calendar_month_utc",
amount: 1,
reservationCents: "2",
notifyEnabled: false,
});
const row = await f.enqueue();
expect(await f.service.process(row)).toMatchObject({
status: "unavailable",
});
expect(f.provider).not.toHaveBeenCalled();
expect(
await db
.select()
.from(costEvents)
.where(eq(costEvents.companyId, f.companyId)),
).toHaveLength(0);
});
it("releases the reservation after a known provider rejection", async () => {
const f = await fixture(),
row = await f.enqueue();
f.provider.mockResolvedValueOnce({
errorCode: "provider_auth_failed",
noProviderWork: true,
receipt: {
inputTokens: 0,
outputTokens: 0,
costCents: "0",
costStatus: "estimated",
providerRequestId: null,
pricingProvenance: { source: "unknown" },
},
});
await f.service.process(row);
expect(
(
await db
.select()
.from(budgetReservations)
.where(eq(budgetReservations.fastResponseRequestId, row.id))
)[0].state,
).toBe("released");
expect(
(
await db
.select()
.from(costEvents)
.where(eq(costEvents.companyId, f.companyId))
)[0].costCents,
).toBe(0);
});
it("tests the fixed sample without posting a conversation message", async () => {
const f = await fixture();
const result = await f.service.test(f.companyId, "alice");
expect(result.status).toBe("succeeded");
expect(f.provider.mock.calls[0][0].prompt).toContain("settings panel");
expect(
await db
.select()
.from(issueComments)
.where(eq(issueComments.companyId, f.companyId)),
).toHaveLength(1);
});
});