mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 12:37:55 +01:00
Tighten recovery resolution contract
This commit is contained in:
1 parent
5859f22f5a
commit
4614547f3f
4 files changed
+20
-8
No files matched your search
@@ -64,6 +64,12 @@ describe("issue validators", () => {
|
||||
sourceIssueStatus: "blocked",
|
||||
}).success,
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
resolveIssueRecoveryActionSchema.safeParse({
|
||||
outcome: "false_positive",
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("allows cancelled recovery resolutions to atomically restore the source issue status", () => {
|
||||
@@ -83,6 +89,12 @@ describe("issue validators", () => {
|
||||
sourceIssueStatus: "blocked",
|
||||
}).success,
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
resolveIssueRecoveryActionSchema.safeParse({
|
||||
outcome: "cancelled",
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects recovery outcomes that are not supported by the source-scoped resolution endpoint", () => {
|
||||
|
||||
@@ -239,8 +239,6 @@ export const resolveIssueRecoveryActionSchema = z.object({
|
||||
|
||||
if (value.outcome === "false_positive" || value.outcome === "cancelled") {
|
||||
if (
|
||||
value.sourceIssueStatus !== undefined &&
|
||||
value.sourceIssueStatus !== null &&
|
||||
value.sourceIssueStatus !== "done" &&
|
||||
value.sourceIssueStatus !== "in_review"
|
||||
) {
|
||||
|
||||
@@ -645,7 +645,7 @@ describeEmbeddedPostgres("issue recovery actions", () => {
|
||||
expect(await recoveryActionSvc.getActiveForIssue(companyId, sourceIssueId)).toBeNull();
|
||||
});
|
||||
|
||||
it("records false positives without cancelling the source issue", async () => {
|
||||
it("rejects false-positive recovery resolution without an explicit source issue status", async () => {
|
||||
const { companyId, managerId, sourceIssueId } = await seedCompany();
|
||||
const recoveryActionSvc = issueRecoveryActionService(db);
|
||||
const action = await recoveryActionSvc.upsertSourceScoped({
|
||||
@@ -669,7 +669,7 @@ describeEmbeddedPostgres("issue recovery actions", () => {
|
||||
outcome: "false_positive",
|
||||
resolutionNote: "The source issue still has a live execution path.",
|
||||
})
|
||||
.expect(200);
|
||||
.expect(400);
|
||||
|
||||
const [sourceIssue] = await db.select().from(issues).where(eq(issues.id, sourceIssueId));
|
||||
expect(sourceIssue?.status).toBe("in_progress");
|
||||
@@ -679,9 +679,9 @@ describeEmbeddedPostgres("issue recovery actions", () => {
|
||||
.from(issueRecoveryActions)
|
||||
.where(eq(issueRecoveryActions.id, action.id));
|
||||
expect(actionRow).toMatchObject({
|
||||
status: "resolved",
|
||||
outcome: "false_positive",
|
||||
resolutionNote: "The source issue still has a live execution path.",
|
||||
status: "active",
|
||||
outcome: null,
|
||||
resolutionNote: null,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ const ACTIVE_RECOVERY_ACTION_STATUSES = ["active", "escalated"] as const satisfi
|
||||
const MAX_UPSERT_RETRIES = 3;
|
||||
|
||||
type IssueRecoveryActionRow = typeof issueRecoveryActions.$inferSelect;
|
||||
type DbTransaction = Parameters<Parameters<Db["transaction"]>[0]>[0];
|
||||
type DbOrTransaction = Db | DbTransaction;
|
||||
|
||||
export type UpsertIssueRecoveryActionInput = {
|
||||
companyId: string;
|
||||
@@ -225,7 +227,7 @@ export function issueRecoveryActionService(db: Db) {
|
||||
|
||||
async function resolveActiveForIssue(
|
||||
input: ResolveIssueRecoveryActionInput,
|
||||
dbOrTx: any = db,
|
||||
dbOrTx: DbOrTransaction = db,
|
||||
): Promise<IssueRecoveryAction | null> {
|
||||
const now = new Date();
|
||||
const predicates = [
|
||||
|
||||
Reference in new issue
Block a user