mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 21:49:18 +01:00
test(server): verify monitor milestone admission from saved receipts
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
2d971ceb5a
commit
db66471936
2 files changed
+66
No files matched your search
@@ -627,3 +627,38 @@ Fresh origin/master inspected at `8c6bd5a83b`; reconciliation of the stack remai
|
||||
outstanding. Publication/interaction, conversation/configuration and monitor
|
||||
receipt producers remain under audit. Production registration and periodic-loop
|
||||
removal are still pending, so this checkpoint is not a completed migration PR.
|
||||
|
||||
## Monitor receipt ordering checkpoint
|
||||
|
||||
The normal native monitor owner does not need another milestone signal when it
|
||||
saves a schedule. `PaperclipRunnerToolAuthority.#setTaskMonitor` writes the issue
|
||||
monitor and its semantic tool receipt inside the same `#withMutationReceipt`
|
||||
transaction. The future deadline cannot be consumed before that commit. A later
|
||||
monitor wake's real queue admission already signals the issue scope, and the
|
||||
projector validates the committed receipt through `readNativeMonitorSource`.
|
||||
Do not add a redundant signal to the generic semantic tool wrapper merely because
|
||||
the projector reads a receipt.
|
||||
|
||||
The new test executes the actual `set_task_monitor` tool authority, then admits
|
||||
the due wake through the real heartbeat queue after the source has finished. It
|
||||
checks the queued notice and idle quiet. Source completion is fixture-controlled
|
||||
and provider execution is held by task drain; this is receipt/admission/lineage
|
||||
coverage, not full monitor scheduler or harness acceptance. Alternate monitor
|
||||
wake rewrites and recovery paths still require audit before the whole producer
|
||||
family is declared complete.
|
||||
|
||||
The initial proposed late-receipt test seeded an admitted wake before creating
|
||||
its future monitor, which did not represent the normal ordering. Its first run
|
||||
also failed the actual future-deadline validator after advancing the test clock
|
||||
(`/tmp/sleep28-monitor-receipt-before.log`). That failure was a fixture problem,
|
||||
not a production regression. The replacement follows the real ownership order
|
||||
instead of manufacturing a missing signal for an unsupported ordering.
|
||||
|
||||
- `/tmp/sleep28-monitor-admission-tests.log`: targeted real producer path passes.
|
||||
- `/tmp/sleep28-monitor-final-tests.log`: all 43 milestone worker cases pass.
|
||||
- No production monitor code changed; `git diff --check` passed.
|
||||
|
||||
Remote gates refreshed at this checkpoint: #17490 at `31bcd1988823` still has
|
||||
failed E2E shard 3 and aggregate checks; #17590 at `e501d6c99d72` still has queued
|
||||
Paperclip Review; #17592 at `f53ae3d6d27c` still has neutral Paperclip Review.
|
||||
These are not claimed ready and no merge was attempted.
|
||||
@@ -21,6 +21,7 @@ import { CHAT_CONTROL_RECOVERY_ADMISSION_KEY, CHAT_CONTROL_RECOVERY_UNRESOLVED_C
|
||||
import { heartbeatService } from "../services/heartbeat.js";
|
||||
import { commitNativeStatusDecision, dispatchPendingNativeStatusEffects } from "../services/native-runtime/status-decision-committer.js";
|
||||
import { claimQueuedNativeReviewRun } from "../services/native-runtime/native-review-dispatch.js";
|
||||
import { PaperclipRunnerToolAuthority } from "../services/native-runtime/paperclip-runner-tool-authority.js";
|
||||
import { finalizeNativeRun } from "../services/native-runtime/native-run-finalizer.js";
|
||||
import { idleWorkSnapshot } from "../services/task-admission.js";
|
||||
|
||||
@@ -385,6 +386,36 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
|
||||
await service.releaseRunClaimedJustBeforeSuppression(seeded.id);
|
||||
await quiet();
|
||||
});
|
||||
it("wakes an admitted monitor through its previously committed source receipt", async () => {
|
||||
const source = await run(f, { runtimeMode: "native", nativeIssueId: f.issueId });
|
||||
await db.update(issues).set({ executionRunId: source.id }).where(eq(issues.id, f.issueId));
|
||||
await start(); await flush(); await quiet();
|
||||
const nextCheckAt = new Date(Date.now() + 60_000).toISOString();
|
||||
const authority = new PaperclipRunnerToolAuthority(db, { ...f, runId: source.id });
|
||||
const request = { tool: "set_task_monitor", callId: randomUUID(), arguments: {
|
||||
idempotencyKey: "milestone-monitor", monitor: { nextCheckAt, notes: "Check the saved task" },
|
||||
} };
|
||||
await expect(authority.execute(request)).resolves.toMatchObject({ taskId: f.issueId, replayed: false });
|
||||
// The monitor and source receipt commit together, before due admission. This
|
||||
// fixture finishes the source without executing a harness; admission below
|
||||
// uses the real queue, with execution held by task drain.
|
||||
await db.update(heartbeatRuns).set({ status: "succeeded" }).where(eq(heartbeatRuns.id, source.id));
|
||||
await db.update(issues).set({ executionRunId: null }).where(eq(issues.id, f.issueId));
|
||||
await quiet();
|
||||
const { service, dependencies } = await queue();
|
||||
dependencies.getSchedulingSuppression.mockResolvedValue({ suppressed: true, reason: "task_drain" });
|
||||
const admitted = await service.enqueueWakeup(f.agentId, { source: "automation", reason: "issue_monitor_due",
|
||||
idempotencyKey: `issue-monitor:${f.issueId}:${nextCheckAt}`,
|
||||
payload: { issueId: f.issueId, nextCheckAt },
|
||||
contextSnapshot: { issueId: f.issueId, source: "issue.monitor" },
|
||||
requestedByActorType: "user", requestedByActorId: f.userId,
|
||||
});
|
||||
expect(admitted).toMatchObject({ status: "queued" });
|
||||
await flush();
|
||||
expect((await publications()).map(row => row.idempotencyKey)).toContain(`run:${admitted!.id}:queued:${f.endpointId}`);
|
||||
expect(dependencies.executeRun).not.toHaveBeenCalled();
|
||||
await quiet();
|
||||
});
|
||||
async function nativeFixture(phase = "committed") {
|
||||
const contractId = randomUUID(), resultId = randomUUID();
|
||||
await db.insert(completionContracts).values({
|
||||
|
||||
Reference in new issue
Block a user