fix(api): read the error code before the status

A read-failure primitive decided from the status alone, so a 401 from a
step-up or re-proof gate would have read as an ended session and sent
the reader to sign-in. The code now decides first and the status is the
fallback for codes it does not know.

The two invitation routes answer an unknown name or e-mail with
sharing.invite.no_account. Both have a second 404 with a different next
step, and since every error now carries a code the clients match on it
instead of on the status. The status defaults move into a module the
client can import, and the contract text no longer says these arms
carry no code.
This commit is contained in:
Marc-André Bombeck committed 2026-10-20 09:48:15 +02:00
1 parent 0057b03699
commit c300574a8e
20 files changed
+212 -128

No files matched your search

@@ -718,16 +718,17 @@ describe("every call the managed-profile card can make", () => {
);
});
it("answers an unknown identifier with a 404 carrying NO error code", async () => {
// The asymmetry the client has to branch on status for. Four arms of this
// route publish a code; this one and the rate limit do not.
it("answers an unknown identifier with a 404 that names itself", async () => {
// This route has a second 404 (`managed_profile.not_found`) with a
// different next step, so the unknown name carries its own code and a
// client never has to tell the two apart by status.
const { creator, profile } = await profileWithPendingSecondGuardian();
signIn(creator);
const response = await inviteGuardian(profile.id, {
identifier: "nobody-here",
});
expect(response.status).toBe(404);
expect(await errorCodeOf(response)).toBeUndefined();
expect(await errorCodeOf(response)).toBe("sharing.invite.no_account");
});
it("refuses an invitation from somebody who is not a Guardian", async () => {
@@ -947,9 +948,9 @@ describe("how many profiles one account may create in an hour", () => {
timezone: "UTC",
});
expect(refused.status).toBe(429);
// No error code, like every other rate-limit refusal in this family — the
// client branches on status, which is recorded in the findings.
expect(await errorCodeOf(refused)).toBeUndefined();
// The status default. A 429 has one meaning in this family (wait), so the
// client branches on the status and needs no code of its own.
expect(await errorCodeOf(refused)).toBe("http.rate_limited");
// And nothing was created by the refused call.
expect(
await getPrismaClient().user.count({
@@ -491,6 +491,8 @@ describe("who may do what to a grant", () => {
const response = await invite("nobody-here@example.test");
expect(response.status).toBe(404);
const body = (await response.json()) as { meta?: { errorCode?: string } };
expect(body.meta?.errorCode).toBe("sharing.invite.no_account");
expect(await getPrismaClient().accountGrant.count()).toBe(0);
});
});