Cap skill stat drain action batches (#2671)

* fix: cap skill stat drain action batches

* test: align content rights correspondence guard
This commit is contained in:
Patrick Erichsen
2026-06-15 18:56:06 -07:00
committed by GitHub
parent e49d680f6c
commit 0db6b17a62
4 changed files with 58 additions and 119 deletions
@@ -94,8 +94,21 @@ bun run admin -- email send \
--body-file /tmp/body.txt --body-file /tmp/body.txt
``` ```
Then, after signoff and successful send, record the exact body with the After explicit signoff, send:
provider id:
```bash
bun run admin -- email send \
--to requester@example.com \
--username Requester \
--subject "Update on ClawHub content rights request" \
--body-file /tmp/body.txt \
--send \
--confirm-user-request \
--confirm-user-signoff \
--json
```
Then record the successful send with the provider id:
```bash ```bash
bun run admin -- content-rights record-correspondence CHR-000007 \ bun run admin -- content-rights record-correspondence CHR-000007 \
+12 -5
View File
@@ -122,7 +122,7 @@ describe("skill stat events - comment delta handling", () => {
); );
}); });
it("floors action drain batch size so stale small continuations do not crawl", async () => { it("bounds action drain work so stale continuations do not crawl or time out", async () => {
const runMutation = vi.fn(async (_ref: unknown, args: Record<string, unknown>) => { const runMutation = vi.fn(async (_ref: unknown, args: Record<string, unknown>) => {
if ("leaseMs" in args) { if ("leaseMs" in args) {
return { return {
@@ -145,15 +145,22 @@ describe("skill stat events - comment delta handling", () => {
await expect( await expect(
processSkillStatEventsInternalHandler( processSkillStatEventsInternalHandler(
{ runMutation, scheduler }, { runMutation, scheduler },
{ batchSize: 10, maxBatches: 1 }, { batchSize: 10, maxBatches: 100 },
), ),
).resolves.toMatchObject({ ).resolves.toMatchObject({
processed: 100, processed: 500,
scheduledContinuation: true, scheduledContinuation: true,
}); });
expect(runMutation.mock.calls[1]?.[1]).toMatchObject({ batchSize: 100 }); const batchCalls = runMutation.mock.calls.filter(([, args]) => {
expect(scheduler.runAfter.mock.calls[0]?.[2]).toMatchObject({ batchSize: 100 }); return args && typeof args === "object" && "leaseOwner" in args && "batchSize" in args;
});
expect(batchCalls).toHaveLength(5);
expect(batchCalls[0]?.[1]).toMatchObject({ batchSize: 100 });
expect(scheduler.runAfter.mock.calls[0]?.[2]).toMatchObject({
batchSize: 100,
maxBatches: 5,
});
}); });
it("aggregates comment and uncomment events into net deltas", () => { it("aggregates comment and uncomment events into net deltas", () => {
+2 -2
View File
@@ -182,8 +182,8 @@ const DOC_SYNC_LEASE_KEY = "skill_doc_stat_sync";
const DOC_SYNC_LEASE_MS = 2 * 60 * 1_000; const DOC_SYNC_LEASE_MS = 2 * 60 * 1_000;
const DEFAULT_DOC_SYNC_BATCH_SIZE = 100; const DEFAULT_DOC_SYNC_BATCH_SIZE = 100;
const MAX_DOC_SYNC_BATCH_SIZE = 100; const MAX_DOC_SYNC_BATCH_SIZE = 100;
const DEFAULT_DOC_SYNC_MAX_BATCHES = 20; const DEFAULT_DOC_SYNC_MAX_BATCHES = 5;
const MAX_DOC_SYNC_MAX_BATCHES = 100; const MAX_DOC_SYNC_MAX_BATCHES = 5;
type ClaimSkillStatDocSyncLeaseResult = type ClaimSkillStatDocSyncLeaseResult =
| { | {
@@ -1,118 +1,37 @@
import { describe, expect, it, vi } from "vitest"; import { readFileSync } from "node:fs";
import { import { join } from "node:path";
buildAdminEmailArgs, import { describe, expect, it } from "vitest";
runCorrespondence,
} from "../.agents/skills/clawhub-content-rights-correspondence/scripts/send-correspondence.js"; const skill = readFileSync(
join(process.cwd(), ".agents/skills/clawhub-content-rights-correspondence/SKILL.md"),
"utf8",
);
const section = (heading: string) => {
const start = skill.indexOf(`## ${heading}`);
expect(start).toBeGreaterThanOrEqual(0);
const next = skill.indexOf("\n## ", start + 1);
return skill.slice(start, next === -1 ? undefined : next);
};
describe("ClawHub content rights correspondence skill", () => { describe("ClawHub content rights correspondence skill", () => {
it("builds a dry-run admin email command by default", () => { it("uses direct admin CLI commands instead of the removed helper script", () => {
expect( expect(skill).toMatch(/Do not use helper\s+scripts/);
buildAdminEmailArgs({ expect(skill).not.toContain("send-correspondence");
to: "legal@example.com", expect(skill).not.toContain("scripts/");
subject: "Re: CHR-000007", expect(skill).toContain("bun run admin -- email send");
bodyFile: "/tmp/body.txt",
send: false,
confirmUserSignoff: false,
}),
).toEqual([
"run",
"admin",
"--",
"email",
"send",
"--to",
"legal@example.com",
"--subject",
"Re: CHR-000007",
"--body-file",
"/tmp/body.txt",
"--json",
]);
}); });
it("refuses to send without explicit user signoff", async () => { it("keeps the explicit send-signoff guard in the requester status update send command", () => {
await expect( const replies = section("Requester Status Updates");
runCorrespondence( expect(replies).toContain("--send");
{ expect(replies).toContain("--confirm-user-request");
caseId: "CHR-000007", expect(replies).toContain("--confirm-user-signoff");
subject: "Re: CHR-000007",
bodyFile: "/tmp/body.txt",
attachments: [],
send: true,
confirmUserSignoff: false,
},
{
exec: vi.fn(),
},
),
).rejects.toThrow("Sending requires --confirm-user-signoff.");
}); });
it("sends to the existing case email and records exact correspondence through Hermit", async () => { it("documents preserving outbound correspondence through the admin CLI", () => {
const execMock = vi expect(skill).toContain("content-rights record-correspondence");
.fn() expect(skill).toContain("--provider-message-id");
.mockResolvedValueOnce({ expect(skill).toContain("--attachment");
stdout: JSON.stringify({
case: { caseId: "CHR-000007", email: "legal@example.com" },
files: [],
events: [],
}),
stderr: "",
exitCode: 0,
})
.mockResolvedValueOnce({
stdout: JSON.stringify({ ok: true, providerId: "email-123" }),
stderr: "",
exitCode: 0,
})
.mockResolvedValueOnce({
stdout: JSON.stringify({ ok: true, caseId: "CHR-000007", storedFiles: 2 }),
stderr: "",
exitCode: 0,
});
await runCorrespondence(
{
caseId: "CHR-000007",
subject: "Re: CHR-000007",
bodyFile: "/tmp/body.txt",
attachments: ["/tmp/response.pdf"],
send: true,
confirmUserSignoff: true,
},
{
exec: execMock,
},
);
expect(execMock).toHaveBeenNthCalledWith(1, [
"run",
"admin",
"--",
"content-rights",
"get",
"CHR-000007",
"--json",
]);
expect(execMock).toHaveBeenNthCalledWith(
2,
expect.arrayContaining(["--to", "legal@example.com", "--send", "--confirm-user-signoff"]),
);
expect(execMock).toHaveBeenNthCalledWith(
3,
expect.arrayContaining([
"content-rights",
"record-correspondence",
"CHR-000007",
"--to",
"legal@example.com",
"--body-file",
"/tmp/body.txt",
"--provider-message-id",
"email-123",
"--attachment",
"/tmp/response.pdf",
]),
);
}); });
}); });