fix: harden activity detail truncation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-07-17 02:28:47 +00:00
1 parent 8ca764a45e
commit 35e3d84be0
5 files changed
+16 -3

No files matched your search

@@ -38,6 +38,7 @@ describe("capActivityDetails", () => {
});
expect(capped?._paperclipOriginalBytes).toBeGreaterThan(MAX_ACTIVITY_DETAILS_BYTES);
expect(capped?._paperclipPreview).toEqual(expect.stringContaining("start-"));
expect(/[\uD800-\uDBFF]$/.test(String(capped?._paperclipPreview))).toBe(false);
expect(Buffer.byteLength(JSON.stringify(capped), "utf8")).toBeLessThanOrEqual(
MAX_ACTIVITY_DETAILS_BYTES,
);
@@ -161,7 +162,7 @@ describeEmbeddedPostgres("activity log retention", () => {
action: "test.oversized",
entityType: "company",
entityId: companyId,
details: { output: "x".repeat(MAX_ACTIVITY_DETAILS_BYTES * 2) },
details: { output: "😀".repeat(MAX_ACTIVITY_DETAILS_BYTES) },
});
const row = await db
+1 -1
View File
@@ -827,7 +827,7 @@ export async function startServer(): Promise<StartedServer> {
let prepareHotRestartShutdown: ((signal: "SIGINT" | "SIGTERM") => Promise<{ skipDrain: boolean }>) | null = null;
let heartbeatSchedulerStopped = false;
let heartbeatSchedulerInterval: ReturnType<typeof setInterval> | null = null;
const stopActivityLogRetention = startActivityLogRetention(db as any);
const stopActivityLogRetention = startActivityLogRetention(db);
const heartbeatSchedulerInFlight = new Set<Promise<void>>();
const trackHeartbeatSchedulerWork = (work: Promise<unknown>) => {
let tracked: Promise<void>;
+9 -1
View File
@@ -99,7 +99,15 @@ export function capActivityDetails(
}
}
return buildMarker(serialized.slice(0, low));
const finalCodeUnit = serialized.charCodeAt(low - 1);
const nextCodeUnit = serialized.charCodeAt(low);
const splitsSurrogatePair = finalCodeUnit >= 0xd800
&& finalCodeUnit <= 0xdbff
&& nextCodeUnit >= 0xdc00
&& nextCodeUnit <= 0xdfff;
const previewEnd = splitsSurrogatePair ? low - 1 : low;
return buildMarker(serialized.slice(0, previewEnd));
}
export async function resolveResponsibleUserIdForActivity(db: Db, input: LogActivityInput) {
+1
View File
@@ -39,6 +39,7 @@ function makeCompany(id: string): Company {
budgetMonthlyCents: 0,
spentMonthlyCents: 0,
attachmentMaxBytes: 10 * 1024 * 1024,
activityLogRetentionDays: null,
defaultResponsibleUserId: null,
requireBoardApprovalForNewAgents: false,
feedbackDataSharingEnabled: false,
+3
View File
@@ -43,6 +43,7 @@ export const storybookCompanies: Company[] = [
budgetMonthlyCents: 250_000,
spentMonthlyCents: 67_500,
attachmentMaxBytes: 10 * 1024 * 1024,
activityLogRetentionDays: null,
defaultResponsibleUserId: "user-board",
requireBoardApprovalForNewAgents: true,
feedbackDataSharingEnabled: true,
@@ -67,6 +68,7 @@ export const storybookCompanies: Company[] = [
budgetMonthlyCents: 180_000,
spentMonthlyCents: 39_500,
attachmentMaxBytes: 10 * 1024 * 1024,
activityLogRetentionDays: null,
defaultResponsibleUserId: "user-board",
requireBoardApprovalForNewAgents: false,
feedbackDataSharingEnabled: false,
@@ -91,6 +93,7 @@ export const storybookCompanies: Company[] = [
budgetMonthlyCents: 90_000,
spentMonthlyCents: 91_200,
attachmentMaxBytes: 10 * 1024 * 1024,
activityLogRetentionDays: null,
defaultResponsibleUserId: "user-board",
requireBoardApprovalForNewAgents: true,
feedbackDataSharingEnabled: false,