From 9a5c2b8ec1ce65f4105b6cb03c136f89f1f2f67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 20 Oct 2026 11:00:25 +0200 Subject: [PATCH] fix(api): keep data.truncated on the account activity feed, deprecated v1.43.6 shipped truncated as a required field of the activity response's data, and moving it to meta alone would break a client built against that in a patch release. data.truncated is back with the same value as meta.truncated and is marked deprecated in the spec; the web client keeps reading meta. --- docs/api/openapi.yaml | 11 +++++++++-- src/app/api/account/activity/route.ts | 4 ++++ src/lib/openapi/routes/account-sharing.ts | 7 ++++++- tests/integration/sharing-audit-actor.test.ts | 6 +++++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 4629570e2..a967cc72b 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -21437,8 +21437,9 @@ paths: per day with a count, rather than one per request. `retentionDays` rides the response because audit history is purged on a schedule the operator configures: a view that showed the rows without the window would imply a completeness it does not have, and clients must state the number the server sends rather than assume the 365-day - default. Whether the list stopped at its row ceiling is `meta.truncated` (it was a `truncated` field in `data` - before v1.43.7). Not delegable: a grant is not an introduction to the other people in the household." + default. Whether the list stopped at its row ceiling is `meta.truncated`; the `truncated` field in `data` + carries the same value and is deprecated. Not delegable: a grant is not an introduction to the other people in + the household." responses: "200": description: The activity, and how far back it can see. @@ -81393,9 +81394,15 @@ components: description: How far back this feed can see, in days. Resolved from the instance's audit retention setting (365 by default, operator-configurable), NOT a constant — an instance running a 90-day window sends 90. The query is bounded to this window, so it is the real limit of the list and not only a caption. Show it beside the rows. + truncated: + type: boolean + deprecated: true + description: "Deprecated: read `meta.truncated`, which carries the same value. Whether the list stopped at its row + ceiling; kept in `data` for clients built against v1.43.6 and earlier." required: - entries - retentionDays + - truncated additionalProperties: false description: The owner's record of what somebody else did in their record, bounded by the instance's audit retention. RecordActivityEntry: diff --git a/src/app/api/account/activity/route.ts b/src/app/api/account/activity/route.ts index d72b9db82..365584600 100644 --- a/src/app/api/account/activity/route.ts +++ b/src/app/api/account/activity/route.ts @@ -153,6 +153,10 @@ export const GET = apiHandler(async () => { // The bound the view states out loud. Resolved here so the UI never has to // hardcode a number the operator can change. retentionDays, + // Deprecated copy of `meta.truncated`. It was a required field of `data` + // through v1.43.6, and a client built against that contract must keep + // decoding it; it goes in a minor release. The web reads `meta`. + truncated: cap.truncated, }, // Whether the list is everything inside that window, or its most recent // hundred rows, rides the envelope like every capped list's (`meta`). diff --git a/src/lib/openapi/routes/account-sharing.ts b/src/lib/openapi/routes/account-sharing.ts index d0bc27ec7..a42bc4a88 100644 --- a/src/lib/openapi/routes/account-sharing.ts +++ b/src/lib/openapi/routes/account-sharing.ts @@ -354,6 +354,11 @@ const recordActivityResponse = z .describe( "How far back this feed can see, in days. Resolved from the instance's audit retention setting (365 by default, operator-configurable), NOT a constant — an instance running a 90-day window sends 90. The query is bounded to this window, so it is the real limit of the list and not only a caption. Show it beside the rows.", ), + truncated: z.boolean().meta({ + deprecated: true, + description: + "Deprecated: read `meta.truncated`, which carries the same value. Whether the list stopped at its row ceiling; kept in `data` for clients built against v1.43.6 and earlier.", + }), }) .meta({ id: "RecordActivity", @@ -870,7 +875,7 @@ export const accountSharingPaths: NonNullable = { tags: ["Account sharing"], summary: "Who opened this record, and when", description: - "Every audit row filed under this account that somebody else performed, newest first — one row per delegate per day with a count, rather than one per request. `retentionDays` rides the response because audit history is purged on a schedule the operator configures: a view that showed the rows without the window would imply a completeness it does not have, and clients must state the number the server sends rather than assume the 365-day default. Whether the list stopped at its row ceiling is `meta.truncated` (it was a `truncated` field in `data` before v1.43.7). Not delegable: a grant is not an introduction to the other people in the household.", + "Every audit row filed under this account that somebody else performed, newest first — one row per delegate per day with a count, rather than one per request. `retentionDays` rides the response because audit history is purged on a schedule the operator configures: a view that showed the rows without the window would imply a completeness it does not have, and clients must state the number the server sends rather than assume the 365-day default. Whether the list stopped at its row ceiling is `meta.truncated`; the `truncated` field in `data` carries the same value and is deprecated. Not delegable: a grant is not an introduction to the other people in the household.", responses: { ...stdResponses, "200": { diff --git a/tests/integration/sharing-audit-actor.test.ts b/tests/integration/sharing-audit-actor.test.ts index f29121eb2..57874c92f 100644 --- a/tests/integration/sharing-audit-actor.test.ts +++ b/tests/integration/sharing-audit-actor.test.ts @@ -462,6 +462,7 @@ describe("the activity feed states its own limits", () => { data: { entries: { action: string }[]; retentionDays: number; + truncated: boolean; }; meta: { truncated: boolean; limit: number }; }; @@ -511,7 +512,9 @@ describe("the activity feed states its own limits", () => { expect(small.data.entries).toHaveLength(3); // The cap rides the envelope, like every capped list's. expect(small.meta.truncated).toBe(false); - expect(small.data).not.toHaveProperty("truncated"); + // The field v1.43.6 shipped in `data` stays, deprecated, with the same + // value. + expect(small.data.truncated).toBe(false); // Over it: same shape, different claim. await getPrismaClient().auditLog.createMany({ @@ -525,5 +528,6 @@ describe("the activity feed states its own limits", () => { const large = await feed(); expect(large.data.entries).toHaveLength(100); expect(large.meta).toMatchObject({ truncated: true, limit: 100 }); + expect(large.data.truncated).toBe(true); }); });