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.
This commit is contained in:
Marc-André Bombeck committed 2026-10-20 11:00:25 +02:00
1 parent 2be262d574
commit 9a5c2b8ec1
4 files changed
+24 -4

No files matched your search

+9 -2
View File
@@ -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:
+4
View File
@@ -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`).
+6 -1
View File
@@ -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<ZodOpenApiObject["paths"]> = {
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": {
@@ -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);
});
});