diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml
index 896cdae75..464b9e4b3 100644
--- a/docs/api/openapi.yaml
+++ b/docs/api/openapi.yaml
@@ -36712,12 +36712,20 @@ components:
banner can name the person without joining two fields. Null when the caller is in their own record, and
ALWAYS null on the Bearer transport: a token carries its selector per request and this endpoint refuses
one."
+ recordKind:
+ type: string
+ enum:
+ - self
+ - shared
+ - managed
+ description: The server-resolved kind of the record currently in view. `self` applies when `active` is null.
canSwitch:
type: boolean
description: Whether there is anywhere to switch to. Bind it directly.
required:
- accounts
- active
+ - recordKind
- canSwitch
additionalProperties: false
description: "Account sharing, resolved. This block is the ONLY source of switchability and writability: the server
@@ -36743,9 +36751,8 @@ components:
enum:
- read
- write
- - manage
- description: The grant's resolved level, under the name v1.36.0 published. Identical to `level` — both come from one
- server-side expression — and kept because shipped clients read it. New code reads `level`.
+ description: The legacy grant level that shipped clients decode. MANAGE serializes as `write` here; new code reads
+ canonical `level`.
level:
type: string
enum:
@@ -36755,6 +36762,13 @@ components:
description: The grant's resolved level. `manage` additionally admits changing and removing entries on the delegable
surfaces; it never reaches the identity surfaces (settings, connections, tokens, consent, grant management),
which stay refused on an invited record at every level. Render it; never derive it from `canWrite`.
+ recordKind:
+ type: string
+ enum:
+ - self
+ - shared
+ - managed
+ description: Server-resolved presentation metadata for the record. It is never accepted as an authorization input.
sections:
anyOf:
- type: array
@@ -36778,6 +36792,7 @@ components:
- displayName
- access
- level
+ - recordKind
- sections
- canWrite
additionalProperties: false
diff --git a/messages/de.json b/messages/de.json
index 9a2f19e82..388c99a38 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -9520,6 +9520,8 @@
"description": "Akten, die andere mit dir geteilt haben.",
"levelRead": "Darf lesen",
"levelWrite": "Darf lesen und hinzufügen",
+ "kindShared": "Geteilte Akte",
+ "kindManaged": "Verwaltetes Profil",
"open": "Akte von {name} öffnen"
},
"toast": {
diff --git a/messages/en.json b/messages/en.json
index e30323d2d..48f404d69 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -9520,6 +9520,8 @@
"description": "Records other people have shared with you.",
"levelRead": "Can view",
"levelWrite": "Can view and add",
+ "kindShared": "Shared record",
+ "kindManaged": "Managed profile",
"open": "Open {name}'s record"
},
"toast": {
diff --git a/messages/es.json b/messages/es.json
index eb7ba6b3f..bbd66c8fa 100644
--- a/messages/es.json
+++ b/messages/es.json
@@ -9520,6 +9520,8 @@
"description": "Historiales que otras personas han compartido contigo.",
"levelRead": "Puede ver",
"levelWrite": "Puede ver y añadir",
+ "kindShared": "Registro compartido",
+ "kindManaged": "Perfil administrado",
"open": "Abrir el historial de {name}"
},
"toast": {
diff --git a/messages/fr.json b/messages/fr.json
index 13e690d84..9f68a3ef5 100644
--- a/messages/fr.json
+++ b/messages/fr.json
@@ -9520,6 +9520,8 @@
"description": "Les dossiers que d'autres personnes partagent avec vous.",
"levelRead": "Peut consulter",
"levelWrite": "Peut consulter et ajouter",
+ "kindShared": "Dossier partagé",
+ "kindManaged": "Profil géré",
"open": "Ouvrir le dossier de {name}"
},
"toast": {
diff --git a/messages/it.json b/messages/it.json
index aec50ff84..c37fdb228 100644
--- a/messages/it.json
+++ b/messages/it.json
@@ -9520,6 +9520,8 @@
"description": "Cartelle che altre persone hanno condiviso con te.",
"levelRead": "Può consultare",
"levelWrite": "Può consultare e aggiungere",
+ "kindShared": "Cartella condivisa",
+ "kindManaged": "Profilo gestito",
"open": "Apri la cartella di {name}"
},
"toast": {
diff --git a/messages/pl.json b/messages/pl.json
index dba4b4487..e97a5d6ea 100644
--- a/messages/pl.json
+++ b/messages/pl.json
@@ -9520,6 +9520,8 @@
"description": "Karty, które udostępniły ci inne osoby.",
"levelRead": "Może przeglądać",
"levelWrite": "Może przeglądać i dodawać",
+ "kindShared": "Udostępniona karta",
+ "kindManaged": "Zarządzany profil",
"open": "Otwórz kartę użytkownika {name}"
},
"toast": {
diff --git a/src/__tests__/account-payload-consumer-guard.test.ts b/src/__tests__/account-payload-consumer-guard.test.ts
index e4c2fda78..f19b838f7 100644
--- a/src/__tests__/account-payload-consumer-guard.test.ts
+++ b/src/__tests__/account-payload-consumer-guard.test.ts
@@ -80,6 +80,19 @@ const NOT_A_CONSUMER = new Set([
join(SRC, "app", "api", "auth", "me", "route.ts"),
]);
+const ACCOUNT_ENTRY_CONSUMER = join(
+ SRC,
+ "components",
+ "dashboard",
+ "looking-after-card.tsx",
+);
+const ACTIVE_RECORD_CONSUMER = join(SRC, "hooks", "use-record-capabilities.ts");
+const ACCOUNT_ACCESS_ADDITIVE_FIELDS = [
+ "level",
+ "sections",
+ "recordKind",
+] as const;
+
/**
* Fields with no client reader, each with the reason it is nonetheless
* correct for the payload to carry it. An entry here is a claim; write one
@@ -192,4 +205,24 @@ describe("account payload consumer guard", () => {
expect(reason.length).toBeGreaterThan(20);
}
});
+
+ it("has non-zero entry and active-record readers for additive access fields", () => {
+ const entrySource = stripComments(
+ readFileSync(ACCOUNT_ENTRY_CONSUMER, "utf8"),
+ );
+ const activeSource = stripComments(
+ readFileSync(ACTIVE_RECORD_CONSUMER, "utf8"),
+ );
+
+ const readerCount = ACCOUNT_ACCESS_ADDITIVE_FIELDS.reduce(
+ (count, field) => {
+ expect(entrySource).toMatch(new RegExp(`entry\\.${field}(?![\\w$])`));
+ expect(activeSource).toMatch(new RegExp(`active\\.${field}(?![\\w$])`));
+ return count + 2;
+ },
+ 0,
+ );
+
+ expect(readerCount).toBeGreaterThan(0);
+ });
});
diff --git a/src/app/medications/__tests__/take-all-due-affordance.test.tsx b/src/app/medications/__tests__/take-all-due-affordance.test.tsx
index 5c3694151..1454d53b5 100644
--- a/src/app/medications/__tests__/take-all-due-affordance.test.tsx
+++ b/src/app/medications/__tests__/take-all-due-affordance.test.tsx
@@ -56,6 +56,7 @@ const OWNER = {
access: "read" as const,
level: "read" as const,
sections: null,
+ recordKind: "shared" as const,
canWrite: false,
};
diff --git a/src/components/__tests__/delegated-write-affordances.test.tsx b/src/components/__tests__/delegated-write-affordances.test.tsx
index f5a786503..524605f27 100644
--- a/src/components/__tests__/delegated-write-affordances.test.tsx
+++ b/src/components/__tests__/delegated-write-affordances.test.tsx
@@ -39,6 +39,7 @@ const OWNER = {
access: "read" as const,
level: "read" as const,
sections: null,
+ recordKind: "shared" as const,
canWrite: false,
};
diff --git a/src/components/charts/__tests__/chart-overlay-controls.test.tsx b/src/components/charts/__tests__/chart-overlay-controls.test.tsx
index 15bc0838c..8eca455f6 100644
--- a/src/components/charts/__tests__/chart-overlay-controls.test.tsx
+++ b/src/components/charts/__tests__/chart-overlay-controls.test.tsx
@@ -10,6 +10,7 @@ const OWNER = {
access: "write" as const,
level: "write" as const,
sections: null,
+ recordKind: "shared" as const,
canWrite: true,
};
diff --git a/src/components/dashboard/__tests__/looking-after-card.test.tsx b/src/components/dashboard/__tests__/looking-after-card.test.tsx
index 2358a02d1..9cfe7e1f2 100644
--- a/src/components/dashboard/__tests__/looking-after-card.test.tsx
+++ b/src/components/dashboard/__tests__/looking-after-card.test.tsx
@@ -27,6 +27,7 @@ const GRANDMOTHER = {
access: "read" as const,
level: "read" as const,
sections: null,
+ recordKind: "shared" as const,
canWrite: false,
};
const FATHER = {
@@ -36,6 +37,7 @@ const FATHER = {
access: "write" as const,
level: "write" as const,
sections: null,
+ recordKind: "shared" as const,
canWrite: true,
};
diff --git a/src/components/dashboard/looking-after-card.tsx b/src/components/dashboard/looking-after-card.tsx
index 704e69433..c95d6f94b 100644
--- a/src/components/dashboard/looking-after-card.tsx
+++ b/src/components/dashboard/looking-after-card.tsx
@@ -97,6 +97,12 @@ export function LookingAfterCard() {
{levelLabel(entry, t)}
+
+ {recordKindLabel(entry, t)}
+
+
+ {scopeLabel(entry, t)}
+
{switchAccount.isPending && (
string,
): string {
+ if (entry.level === "manage") {
+ return t("recordSharing.row.canManage");
+ }
return entry.canWrite
? t("recordSharing.lookingAfter.levelWrite")
: t("recordSharing.lookingAfter.levelRead");
}
+
+function recordKindLabel(
+ entry: AccountAccessEntry,
+ t: (key: string) => string,
+): string {
+ return entry.recordKind === "managed"
+ ? t("recordSharing.lookingAfter.kindManaged")
+ : t("recordSharing.lookingAfter.kindShared");
+}
+
+function scopeLabel(
+ entry: AccountAccessEntry,
+ t: (key: string) => string,
+): string {
+ if (entry.sections === null) {
+ return t("recordSharing.row.entireRecord");
+ }
+ if (entry.sections.length === 0) {
+ return t("recordSharing.row.noSections");
+ }
+ return entry.sections
+ .map((domain) => t(`recordSharing.section.${domain}.label`))
+ .join(", ");
+}
diff --git a/src/components/layout/__tests__/auth-shell-shared-record-doors.test.tsx b/src/components/layout/__tests__/auth-shell-shared-record-doors.test.tsx
index d5f5166ac..7e05b31b5 100644
--- a/src/components/layout/__tests__/auth-shell-shared-record-doors.test.tsx
+++ b/src/components/layout/__tests__/auth-shell-shared-record-doors.test.tsx
@@ -117,6 +117,7 @@ const OWNER_READ: AccountAccessEntry = {
access: "read" as const,
level: "read" as const,
sections: null,
+ recordKind: "shared" as const,
canWrite: false,
};
const OWNER_WRITE: AccountAccessEntry = {
diff --git a/src/components/layout/__tests__/shared-record-chrome.test.tsx b/src/components/layout/__tests__/shared-record-chrome.test.tsx
index eff5e8653..a6c8a9651 100644
--- a/src/components/layout/__tests__/shared-record-chrome.test.tsx
+++ b/src/components/layout/__tests__/shared-record-chrome.test.tsx
@@ -59,6 +59,7 @@ const OWNER = {
access: "read" as const,
level: "read" as const,
sections: null,
+ recordKind: "shared" as const,
canWrite: false,
};
diff --git a/src/hooks/__tests__/use-record-capabilities.test.ts b/src/hooks/__tests__/use-record-capabilities.test.ts
index ed6aac0d0..90dc83abc 100644
--- a/src/hooks/__tests__/use-record-capabilities.test.ts
+++ b/src/hooks/__tests__/use-record-capabilities.test.ts
@@ -27,6 +27,7 @@ const READ_ONLY: AccountAccessEntry = {
access: "read",
level: "read",
sections: null,
+ recordKind: "shared",
canWrite: false,
};
@@ -39,7 +40,7 @@ const WRITABLE: AccountAccessEntry = {
const MANAGING: AccountAccessEntry = {
...READ_ONLY,
- access: "manage",
+ access: "write",
level: "manage",
canWrite: true,
};
@@ -64,6 +65,7 @@ describe("resolveRecordCapabilities", () => {
// apart from a real one.
level: null,
sections: null,
+ recordKind: "self",
});
expect(resolveRecordCapabilities(undefined)).toEqual(
resolveRecordCapabilities(null),
@@ -78,6 +80,7 @@ describe("resolveRecordCapabilities", () => {
canManage: false,
level: "read",
sections: null,
+ recordKind: "shared",
});
});
@@ -91,6 +94,7 @@ describe("resolveRecordCapabilities", () => {
canManage: false,
level: "write",
sections: null,
+ recordKind: "shared",
});
});
@@ -121,6 +125,7 @@ describe("resolveRecordCapabilities", () => {
canManage: false,
level: "manage",
sections: null,
+ recordKind: "shared",
});
});
diff --git a/src/hooks/use-auth.ts b/src/hooks/use-auth.ts
index 39927b406..53cd921f7 100644
--- a/src/hooks/use-auth.ts
+++ b/src/hooks/use-auth.ts
@@ -34,6 +34,7 @@ import type { AccountAccess } from "@/lib/sharing/account-access-view";
export const NO_ACCOUNT_ACCESS: AccountAccess = {
accounts: [],
active: null,
+ recordKind: "self",
canSwitch: false,
};
@@ -347,6 +348,12 @@ function coerceAccountAccess(value: unknown): AccountAccess {
return {
accounts: block.accounts,
active: block.active ?? null,
+ recordKind:
+ block.recordKind === "self" ||
+ block.recordKind === "shared" ||
+ block.recordKind === "managed"
+ ? block.recordKind
+ : "self",
canSwitch: block.canSwitch === true,
};
}
diff --git a/src/hooks/use-record-capabilities.ts b/src/hooks/use-record-capabilities.ts
index b9370eb2a..f0d2ecb7c 100644
--- a/src/hooks/use-record-capabilities.ts
+++ b/src/hooks/use-record-capabilities.ts
@@ -98,6 +98,8 @@ export interface RecordCapabilities {
* nothing; it is a real answer and reads as one.
*/
sections: AccountAccessEntry["sections"];
+ /** The server-resolved kind of the record currently in view. */
+ recordKind: AccountAccessEntry["recordKind"] | "self";
}
/**
@@ -122,6 +124,7 @@ export function resolveRecordCapabilities(
// to tell those apart would have to guess.
level: null,
sections: null,
+ recordKind: "self",
};
}
return {
@@ -131,6 +134,7 @@ export function resolveRecordCapabilities(
canManage: false,
level: active.level,
sections: active.sections,
+ recordKind: active.recordKind,
};
}
diff --git a/src/lib/insights/__tests__/coach-launch-context.test.tsx b/src/lib/insights/__tests__/coach-launch-context.test.tsx
index 391d8e14a..b294d8ad7 100644
--- a/src/lib/insights/__tests__/coach-launch-context.test.tsx
+++ b/src/lib/insights/__tests__/coach-launch-context.test.tsx
@@ -32,6 +32,7 @@ const OWNER = {
access: "write" as const,
level: "write" as const,
sections: null,
+ recordKind: "shared" as const,
canWrite: true,
};
diff --git a/src/lib/openapi/routes/account-sharing.ts b/src/lib/openapi/routes/account-sharing.ts
index 3d1a08f23..ecba5c64d 100644
--- a/src/lib/openapi/routes/account-sharing.ts
+++ b/src/lib/openapi/routes/account-sharing.ts
@@ -220,15 +220,20 @@ const accountAccessEntry = z
username: z.string(),
displayName: z.string().nullable(),
access: z
- .enum(["read", "write", "manage"])
+ .enum(["read", "write"])
.describe(
- "The grant's resolved level, under the name v1.36.0 published. Identical to `level` — both come from one server-side expression — and kept because shipped clients read it. New code reads `level`.",
+ "The legacy grant level that shipped clients decode. MANAGE serializes as `write` here; new code reads canonical `level`.",
),
level: z
.enum(["read", "write", "manage"])
.describe(
"The grant's resolved level. `manage` additionally admits changing and removing entries on the delegable surfaces; it never reaches the identity surfaces (settings, connections, tokens, consent, grant management), which stay refused on an invited record at every level. Render it; never derive it from `canWrite`.",
),
+ recordKind: z
+ .enum(["self", "shared", "managed"])
+ .describe(
+ "Server-resolved presentation metadata for the record. It is never accepted as an authorization input.",
+ ),
sections: z
.array(shareSection)
.nullable()
@@ -257,6 +262,11 @@ export const accountAccessBlock = z
.describe(
"The record this session is inside right now, resolved to a full entry rather than an id to look up — so a banner can name the person without joining two fields. Null when the caller is in their own record, and ALWAYS null on the Bearer transport: a token carries its selector per request and this endpoint refuses one.",
),
+ recordKind: z
+ .enum(["self", "shared", "managed"])
+ .describe(
+ "The server-resolved kind of the record currently in view. `self` applies when `active` is null.",
+ ),
canSwitch: z
.boolean()
.describe("Whether there is anywhere to switch to. Bind it directly."),
diff --git a/src/lib/sharing/__tests__/account-access.test.ts b/src/lib/sharing/__tests__/account-access.test.ts
new file mode 100644
index 000000000..a1b89c734
--- /dev/null
+++ b/src/lib/sharing/__tests__/account-access.test.ts
@@ -0,0 +1,107 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+vi.mock("@/lib/db", () => ({
+ prisma: {
+ accountGrant: {
+ findMany: vi.fn(),
+ },
+ },
+}));
+
+import { prisma } from "@/lib/db";
+import { resolveAccountAccess } from "../account-access";
+import { decodeLegacyWholeRecordAccess } from "../account-access-view";
+import { LEGACY_ACCOUNT_PAYLOADS } from "../../../../tests/fixtures/v137/legacy-account-payloads";
+
+function activeGrant({
+ access,
+ managedProfileAt = null,
+}: {
+ access: "READ" | "WRITE" | "MANAGE";
+ managedProfileAt?: Date | null;
+}) {
+ return {
+ id: `grant-${access.toLowerCase()}`,
+ access,
+ scopeJson: null,
+ acceptedAt: new Date("2026-08-01T00:00:00.000Z"),
+ revokedAt: null,
+ expiresAt: null,
+ grantor: {
+ id: "record-owner",
+ username: "record-owner",
+ displayName: "Record owner",
+ managedProfileAt,
+ },
+ };
+}
+
+beforeEach(() => {
+ vi.clearAllMocks();
+});
+
+describe("resolveAccountAccess", () => {
+ it("keeps MANAGE compatible with legacy access while publishing its canonical level", async () => {
+ vi.mocked(prisma.accountGrant.findMany).mockResolvedValue([
+ activeGrant({
+ access: "MANAGE",
+ managedProfileAt: new Date("2026-08-01T00:00:00.000Z"),
+ }),
+ ] as never);
+
+ const access = await resolveAccountAccess({
+ user: { id: "delegate" },
+ session: { actingAsUserId: "record-owner" },
+ });
+
+ expect(access.accounts).toEqual([
+ expect.objectContaining({
+ access: "write",
+ level: "manage",
+ sections: null,
+ canWrite: true,
+ recordKind: "managed",
+ }),
+ ]);
+ expect(access.active).toEqual(
+ expect.objectContaining({
+ level: "manage",
+ sections: null,
+ recordKind: "managed",
+ }),
+ );
+ expect(access.recordKind).toBe("managed");
+ });
+
+ it("resolves an ordinary shared record on the server", async () => {
+ vi.mocked(prisma.accountGrant.findMany).mockResolvedValue([
+ activeGrant({ access: "READ" }),
+ ] as never);
+
+ const access = await resolveAccountAccess({
+ user: { id: "delegate" },
+ session: {},
+ });
+
+ expect(access.accounts[0]).toEqual(
+ expect.objectContaining({
+ access: "read",
+ level: "read",
+ recordKind: "shared",
+ }),
+ );
+ expect(access.recordKind).toBe("self");
+ });
+
+ it("keeps whole-record legacy payloads decodable and scoped payloads closed", () => {
+ for (const fixture of LEGACY_ACCOUNT_PAYLOADS) {
+ const decoded = decodeLegacyWholeRecordAccess(fixture);
+ expect(decoded === null ? "deny" : "allow", fixture.name).toBe(
+ fixture.legacyDecoder,
+ );
+ if (fixture.legacyDecoder === "allow") {
+ expect(decoded).toBe(fixture.access);
+ }
+ }
+ });
+});
diff --git a/src/lib/sharing/account-access-view.ts b/src/lib/sharing/account-access-view.ts
index 167ea0cd4..d2caada5a 100644
--- a/src/lib/sharing/account-access-view.ts
+++ b/src/lib/sharing/account-access-view.ts
@@ -23,6 +23,26 @@ import type { ShareDomain } from "@/lib/sharing/scope";
/** What a grant lets its holder do, resolved. */
export type AccountAccessLevel = "read" | "write" | "manage";
+/** The two values published by the original account-access contract. */
+export type LegacyAccountAccessLevel = "read" | "write";
+
+/** The server-resolved kind of record a presentation is naming. */
+export type AccountRecordKind = "self" | "shared" | "managed";
+
+/**
+ * Read the portion of an account-access entry that an older whole-record
+ * client understands. A scoped entry is refused rather than guessed at.
+ */
+export function decodeLegacyWholeRecordAccess(
+ value: unknown,
+): LegacyAccountAccessLevel | null {
+ if (value === null || typeof value !== "object") return null;
+ const entry = value as { access?: unknown; sections?: unknown };
+ if (entry.access !== "read" && entry.access !== "write") return null;
+ if (entry.sections !== undefined && entry.sections !== null) return null;
+ return entry.access;
+}
+
/** One account this caller may act on. */
export interface AccountAccessEntry {
/** The account whose record it is — the value `POST /api/account/switch` takes. */
@@ -37,13 +57,18 @@ export interface AccountAccessEntry {
* assigned from one expression in `account-access.ts`, so they cannot come
* apart; `level` is the name to write new code against.
*/
- access: AccountAccessLevel;
+ access: LegacyAccountAccessLevel;
/**
* v1.37.0 — the same resolved level, under the name the three-level
* contract speaks. Read it; never derive it from `canWrite`, and never
* derive `canWrite` from it.
*/
level: AccountAccessLevel;
+ /**
+ * What kind of record this is. This is presentation metadata only; it is
+ * resolved by the server and never accepted as an authorization input.
+ */
+ recordKind: AccountRecordKind;
/**
* v1.37.0 — which sections of the record this grant opens, or `null` for
* the entire record.
@@ -81,6 +106,8 @@ export interface AccountAccess {
* and always null on the Bearer transport.
*/
active: AccountAccessEntry | null;
+ /** The server-resolved kind of the record currently in view. */
+ recordKind?: AccountRecordKind;
/** Is there anywhere to switch to. */
canSwitch: boolean;
}
diff --git a/src/lib/sharing/account-access.ts b/src/lib/sharing/account-access.ts
index a60894680..019e80da0 100644
--- a/src/lib/sharing/account-access.ts
+++ b/src/lib/sharing/account-access.ts
@@ -104,24 +104,32 @@ export async function resolveAccountAccess(auth: {
where: { granteeId: actorId, revokedAt: null },
orderBy: { createdAt: "desc" },
include: {
- grantor: { select: { id: true, username: true, displayName: true } },
+ grantor: {
+ select: {
+ id: true,
+ username: true,
+ displayName: true,
+ managedProfileAt: true,
+ },
+ },
},
});
const accounts: AccountAccessEntry[] = rows
.filter((grant) => isGrantActive(grant, now))
.map((grant) => {
- // One expression, two names. `access` is what v1.36.0 published and
- // what shipped clients read; `level` is what the three-level contract
- // calls it. Deriving both from one lookup is what stops them saying
- // different things about the same row.
+ // `access` is the shipped two-level field, so MANAGE remains write for
+ // clients that have not learned the canonical level. `level` names the
+ // complete three-level contract.
const level = LEVEL[grant.access];
return {
accountId: grant.grantor.id,
username: grant.grantor.username,
displayName: grant.grantor.displayName,
- access: level,
+ access: level === "read" ? "read" : "write",
level,
+ recordKind:
+ grant.grantor.managedProfileAt === null ? "shared" : "managed",
sections: resolveGrantSections(grant.scopeJson),
canWrite: grantAllows(grant, "write", now),
};
@@ -132,5 +140,10 @@ export async function resolveAccountAccess(auth: {
? null
: (accounts.find((a) => a.accountId === stamped) ?? null);
- return { accounts, active, canSwitch: accounts.length > 0 };
+ return {
+ accounts,
+ active,
+ recordKind: active?.recordKind ?? "self",
+ canSwitch: accounts.length > 0,
+ };
}