fix(dot): preserve completion and admission receipts

Normalize accepted completion reports across the control plane and Rust bridge, serialize Dot assignments, replay work admission receipts, and continue subscription polling.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-06 22:16:43 -05:00
1 parent ad1ff9b329
commit 78f4c4f5e6
8 files changed
+227 -30

No files matched your search

@@ -75,3 +75,18 @@ it("keeps a revoked binding cleared when the follow-up connection read fails", a
expect(client.getQueryData(["dot-binding", "company", "agent"])).toMatchObject({ binding: null });
expect(Array.from(container.querySelectorAll("button")).some(button => button.textContent === "Revoke connection")).toBe(false);
});
it("shows event testing when a connected Dot subscribes without a manual refresh", async () => {
api.get.mockResolvedValue({ enabled: true, resourceUrl: "https://paperclip.example/mcp/runner", binding: { ...binding, subscriptionVerified: false } });
vi.useFakeTimers();
try {
await act(async () => root.render(<QueryClientProvider client={client}><Form /></QueryClientProvider>));
await act(async () => { await vi.advanceTimersByTimeAsync(20); });
expect(container.textContent).toContain("Event subscription: required");
expect(container.textContent).not.toContain("Test event delivery");
api.get.mockResolvedValue({ enabled: true, resourceUrl: "https://paperclip.example/mcp/runner", binding });
await act(async () => { await vi.advanceTimersByTimeAsync(5020); });
expect(container.textContent).toContain("Test event delivery");
expect(container.textContent).toContain("Event subscription: verified");
} finally { vi.useRealTimers(); }
});
+3 -1
View File
@@ -20,7 +20,9 @@ export function DotRunnerConnection({ companyId, agentId, bindingId, onBinding }
const key = ["dot-binding", companyId, agentId];
const state = useQuery({ queryKey: key, enabled: !!companyId && !!agentId,
queryFn: () => api.get<Connection>(path),
refetchInterval: query => query.state.data?.binding?.status === "pairing" || query.state.data?.binding?.hasPendingChallenge || query.state.data?.binding?.assignment ? 5000 : false });
refetchInterval: query => query.state.data?.binding?.status === "pairing" ||
(query.state.data?.binding?.connected && !query.state.data.binding.subscriptionVerified) ||
query.state.data?.binding?.hasPendingChallenge || query.state.data?.binding?.assignment ? 5000 : false });
const pair = useMutation({ mutationFn: () => api.post<{ bindingId: string; pairingCode: string; expiresAt: string }>(path, {}),
onSuccess: result => { setPairing(result); onBinding(result.bindingId); void client.invalidateQueries({ queryKey: key }); } });
const test = useMutation({ mutationFn: () => api.post(path + "/event-test", {}),