mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-24 10:13:11 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The server heartbeat service dispatches scheduled retries and queued runs. > - The service kept policy decisions and database writes in one large file. > - This layout made policy branches harder to test and transaction boundaries harder to inspect. > - This pull request moves the policy rules and database transactions into a run-dispatch module. > - The benefit is a smaller service, pure policy tests, and clear transaction ownership. ## Linked Issues or Issue Description **What existing behavior does this improve?** The server heartbeat service promotes scheduled retries and cancels stale queued runs. **Subsystem affected** server/ — REST API and orchestration services. **Current behavior** The heartbeat service contains the policy rules and the database writes for these dispatch paths. **Proposed behavior** A run-dispatch module owns pure policy functions and semantic database transactions. The public service contracts stay unchanged. **Reason and benefit** The new layout separates branch rules from database effects. It makes each policy branch easier to test and keeps each operation’s row writes in one transaction. **Breaking changes** None. The public service contracts stay unchanged. ## What Changed - Move scheduled-retry promotion and queued-run staleness rules into pure functions. - Add table-driven unit tests for each policy branch. - Move promotion and cancellation writes into semantic transactions. - Keep row locking, company isolation, and post-commit effects unchanged. ## Verification - `node scripts/check-module-boundaries.mjs` passes. - `tsc --noEmit` from `server/` reports no errors. - The focused server test command passes 228 tests in six files. - Full pull request CI passes. - Greptile reports 5/5, and all review threads are resolved. ## Risks The main risk concerns changed transaction boundaries in scheduled-retry promotion and queued-run cancellation. The focused tests retain coverage for locking, transactionality, company isolation, and transport contracts. The public service contracts do not change. ## Model Used Codex, GPT-5, with code execution and tool use. The context window is not provided by the runtime. ## 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 - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I have addressed all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
81 lines
2.5 KiB
TypeScript
81 lines
2.5 KiB
TypeScript
import type { Db } from "@paperclipai/db";
|
|
import { createPostgresRunDispatchAdapter } from "./adapters/postgres.js";
|
|
import {
|
|
createCancelStaleQueuedRun,
|
|
createDispatchResolvedInteractionIfCurrent,
|
|
createEvaluateScheduledRetryGate,
|
|
createPromoteDueScheduledRetries,
|
|
createPromoteScheduledRetry,
|
|
} from "./application/use-cases.js";
|
|
import type { RunDispatchWriter, ScheduledRetryReader } from "./application/ports.js";
|
|
|
|
export {
|
|
MAX_TURN_CONTINUATION_RETRY_REASON,
|
|
WORKSPACE_BUSY_RETRY_REASON,
|
|
INTERACTION_CONTINUATION_INFRA_RETRY_REASON,
|
|
INTERACTION_CONTINUATION_INFRA_WAKE_REASON,
|
|
WAKE_COMMENT_IDS_KEY,
|
|
RESOLVED_INTERACTION_CONTINUATION_STATUSES,
|
|
isNonAssigneeWorkspaceBusyRetry,
|
|
extractWakeCommentIds,
|
|
deriveCommentId,
|
|
allowsIssueInteractionWake,
|
|
isResolvedInteractionContinuationWakeContext,
|
|
} from "./domain/wake-context.js";
|
|
export type {
|
|
RetryReasonKind,
|
|
BudgetBlockFacts,
|
|
PauseHoldFacts,
|
|
DependencyBlockFacts,
|
|
DispositionRepairFacts,
|
|
ReviewParticipantFacts,
|
|
ScheduledRetryGateErrorCode,
|
|
GateDecision,
|
|
ScheduledRetryFacts,
|
|
QueuedRunStalenessErrorCode,
|
|
StalenessDecision,
|
|
QueuedRunFacts,
|
|
} from "./domain/policy.js";
|
|
export type {
|
|
PostCommitEffect,
|
|
PromoteScheduledRetryOutcome,
|
|
CancelStaleQueuedRunOutcome,
|
|
} from "./application/types.js";
|
|
export { RunDispatchApplicationError } from "./application/types.js";
|
|
|
|
export type RunDispatchDeps = {
|
|
/** Overrides the Postgres adapter; a test builds its module against a fake instead. */
|
|
adapter?: ScheduledRetryReader & RunDispatchWriter;
|
|
};
|
|
|
|
/**
|
|
* Composes the run-dispatch module: the Postgres adapter and its semantic
|
|
* use cases. `heartbeat.ts` holds the only caller: it builds one
|
|
* instance per process and delegates the scheduled-retry promotion gate and
|
|
* the queued-run staleness gate to it.
|
|
*/
|
|
export function createRunDispatch(db: Db, deps: RunDispatchDeps = {}) {
|
|
const adapter = deps.adapter ?? createPostgresRunDispatchAdapter(db);
|
|
|
|
const promoteScheduledRetry = createPromoteScheduledRetry({
|
|
writer: adapter,
|
|
});
|
|
|
|
return {
|
|
evaluateScheduledRetryGate: createEvaluateScheduledRetryGate({ reader: adapter }),
|
|
promoteScheduledRetry,
|
|
promoteDueScheduledRetries: createPromoteDueScheduledRetries({
|
|
reader: adapter,
|
|
promoteScheduledRetry,
|
|
}),
|
|
cancelStaleQueuedRun: createCancelStaleQueuedRun({
|
|
writer: adapter,
|
|
}),
|
|
dispatchResolvedInteractionIfCurrent: createDispatchResolvedInteractionIfCurrent({
|
|
writer: adapter,
|
|
}),
|
|
};
|
|
}
|
|
|
|
export type RunDispatch = ReturnType<typeof createRunDispatch>;
|