fix(settings): a fresh managed connection reads as connected again

The managed-record integration projection mapped the ledger's new `unknown`
state to `disconnected`, but that mapping runs after the connection map has
already established a real credential. A guardian who connected Withings on a
child's record saw "Not connected" until the first hourly sync landed, beside
a row whose own `connected` field said true — an invitation to revoke and redo
the grant they had just made.

`state` on this projection reports that a pipe exists, not that it is live;
liveness is the sync verdict the card beside the pill already reads. With no
ledger row and a credential present, the pipe exists.
This commit is contained in:
Marc-André Bombeck committed 2026-09-11 10:54:56 +02:00
1 parent e991fdf65d
commit 5c194cc9b9
2 files changed
+41 -6

No files matched your search

@@ -171,6 +171,33 @@ describe("record settings contract", () => {
).toBe("error_reauth");
});
it("reports a real credential with no ledger row as connected", () => {
// The connection map is the credential: this branch is only reached once
// it says a pipe exists. `unknown` on top of it means the sync ledger
// holds no row yet — an hourly provider connected minutes ago — and this
// projection's `state` is documented as "a pipe exists", not as liveness.
// Calling it "Not connected" invites a guardian to revoke the grant they
// just made.
const connected = {
withings: true,
whoop: false,
fitbit: false,
nightscout: false,
polar: false,
oura: false,
"google-health": false,
strava: false,
} as const;
expect(
resolveManagedIntegrationState("unknown", connected, "withings"),
).toBe("connected");
// No credential still outranks it.
expect(resolveManagedIntegrationState("unknown", connected, "oura")).toBe(
"disconnected",
);
});
it("rejects a late response for another record in a second tab", () => {
expect(() =>
assertRecordSettingsResponseForRecord(
+14 -6
View File
@@ -5,9 +5,14 @@ import type {
/**
* A status-ledger row alone cannot prove a connection: integrations without a
* row synthesize a healthy ledger state for the owner Settings dashboard. The
* row synthesize a ledger state the owner Settings dashboard reads. The
* managed projection must instead report those as disconnected until its
* record carries a real credential or connection row.
*
* This projection's `state` says whether a pipe exists, never whether it is
* live — liveness is `syncHealth.verdict`, and the card beside this pill
* reads that. So the connection map decides first, and only then does the
* ledger state refine the answer.
*/
export function resolveManagedIntegrationState(
state: IntegrationState,
@@ -15,9 +20,12 @@ export function resolveManagedIntegrationState(
integration: IntegrationKey,
): IntegrationState {
if (!connected[integration]) return "disconnected";
// v1.38.19 — `unknown` is the ledger saying it holds no row. The managed
// projection publishes a closed vocabulary its card paints from, and
// "no history" is not one of its states, so it reads as `disconnected`
// here exactly as it does everywhere else.
return state === "unknown" ? "disconnected" : state;
// v1.38.19 — `unknown` is the ledger saying it holds no row, and past the
// gate above that means a real credential exists whose first sync has not
// landed yet. "A pipe exists" is exactly what this field reports, so it
// reads `connected`. Reporting "Not connected" over a credential the
// guardian created minutes ago invites them to revoke and redo the grant,
// and it would contradict the `connected: true` the same row carries. The
// closed vocabulary this projection publishes stays closed either way.
return state === "unknown" ? "connected" : state;
}