mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
fix(api): keep event cursor projection state internal
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
6710a69333
commit
bf068a427c
3 files changed
+39
-4
No files matched your search
@@ -384,6 +384,30 @@ describeEmbeddedPostgres("heartbeat run privacy routes", { concurrent: false },
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps cursor projection state out of public run responses", async () => {
|
||||
const fixture = await seedFixture();
|
||||
const resultJson = { summary: "Maintenance complete", eventCursorProjectionSeq: "payload value" };
|
||||
await db.update(heartbeatRuns).set({
|
||||
eventCursorProjectionSeq: 41,
|
||||
eventCursorRebuildPending: true,
|
||||
resultJson,
|
||||
}).where(eq(heartbeatRuns.id, fixture.maintenanceRunId));
|
||||
const app = createBoardApp(fixture.companyId, fixture.ownerUserId);
|
||||
const detail = await request(app).get(`/api/heartbeat-runs/${fixture.maintenanceRunId}`).expect(200);
|
||||
const history = await request(app).get(`/api/companies/${fixture.companyId}/heartbeat-runs`).expect(200);
|
||||
const cancelled = await request(app).post(`/api/heartbeat-runs/${fixture.maintenanceRunId}/cancel`).expect(200);
|
||||
const listed = history.body.find((run: { id: string }) => run.id === fixture.maintenanceRunId);
|
||||
for (const response of [detail.body, listed, cancelled.body]) {
|
||||
expect(response).toMatchObject({ id: fixture.maintenanceRunId, status: "succeeded" });
|
||||
expect(response).not.toHaveProperty("eventCursorProjectionSeq");
|
||||
expect(response).not.toHaveProperty("eventCursorRebuildPending");
|
||||
}
|
||||
expect(detail.body.resultJson).toEqual(resultJson);
|
||||
expect(cancelled.body.resultJson).toEqual(resultJson);
|
||||
const [stored] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, fixture.maintenanceRunId));
|
||||
expect(stored).toMatchObject({ eventCursorProjectionSeq: 41, eventCursorRebuildPending: true, resultJson });
|
||||
});
|
||||
|
||||
it("filters private task links from a readable company run while preserving authorized links", async () => {
|
||||
const fixture = await seedFixture();
|
||||
const publicIssueId = randomUUID();
|
||||
|
||||
@@ -299,6 +299,7 @@ import {
|
||||
getHeartbeatRunReadDecisions,
|
||||
canActorReadWorkspaceOperation,
|
||||
redactHeartbeatRunListRow,
|
||||
toPublicHeartbeatRun,
|
||||
} from "../services/heartbeat-run-privacy.js";
|
||||
import {
|
||||
PaperclipRunnerProviderProfileError,
|
||||
@@ -6330,7 +6331,7 @@ export function agentRoutes(
|
||||
});
|
||||
}
|
||||
|
||||
res.status(202).json(run);
|
||||
res.status(202).json(toPublicHeartbeatRun(run));
|
||||
};
|
||||
|
||||
router.post("/agents/:id/wakeup", validate(wakeAgentSchema), async (req, res) => {
|
||||
@@ -6455,7 +6456,7 @@ export function agentRoutes(
|
||||
});
|
||||
}
|
||||
|
||||
res.status(202).json(run);
|
||||
res.status(202).json(toPublicHeartbeatRun(run));
|
||||
});
|
||||
|
||||
router.post("/agents/:id/claude-login", async (req, res) => {
|
||||
@@ -7168,7 +7169,7 @@ export function agentRoutes(
|
||||
run.companyId,
|
||||
run.id,
|
||||
redactCurrentUserValue(
|
||||
{ ...decoratedRun, execution: await executionProjectionForRun(db, run.companyId, run.id), identityHistory: await listRunIdentityContexts(db, run.companyId, run.id), retryExhaustedReason, outputSilence: await heartbeat.buildRunOutputSilence(run) },
|
||||
toPublicHeartbeatRun({ ...decoratedRun, execution: await executionProjectionForRun(db, run.companyId, run.id), identityHistory: await listRunIdentityContexts(db, run.companyId, run.id), retryExhaustedReason, outputSilence: await heartbeat.buildRunOutputSilence(run) }),
|
||||
await getCurrentUserRedactionOptions(),
|
||||
),
|
||||
req.actor.type === "board" ? getActorInfo(req).actorId : null,
|
||||
@@ -7205,7 +7206,7 @@ export function agentRoutes(
|
||||
});
|
||||
}
|
||||
|
||||
res.json(run ? await runRedactions.redactForRun(run.companyId, run.id, run,
|
||||
res.json(run ? await runRedactions.redactForRun(run.companyId, run.id, toPublicHeartbeatRun(run),
|
||||
getActorInfo(req).actorId) : null);
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,16 @@ type RunIssueBinding = {
|
||||
issueId?: string | null;
|
||||
};
|
||||
|
||||
/** Keep derived ingestion state out of the public run response contract. */
|
||||
export function toPublicHeartbeatRun<T extends Record<string, unknown>>(run: T) {
|
||||
const {
|
||||
eventCursorProjectionSeq: _eventCursorProjectionSeq,
|
||||
eventCursorRebuildPending: _eventCursorRebuildPending,
|
||||
...response
|
||||
} = run;
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function canActorReadHeartbeatRun(
|
||||
db: Db,
|
||||
access: {
|
||||
|
||||
Reference in new issue
Block a user