Skip to content

Commit cecdf4b

Browse files
fix(server): read lineage snapshots atomically
1 parent e3697c0 commit cecdf4b

1 file changed

Lines changed: 73 additions & 57 deletions

File tree

packages/server/src/storage/sqlite.ts

Lines changed: 73 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,65 +1564,81 @@ class SqliteIdentityStorage implements IdentityStorage {
15641564
};
15651565
}
15661566

1567-
const lineage = backend.db
1568-
.prepare<IdentityLineageRow>(SELECT_IDENTITY_LINEAGE_SQL)
1569-
.get(normalizedIdentityId);
1570-
if (!lineage) {
1571-
return null;
1572-
}
1573-
1574-
const identityRecord = hydrateIdentityLineage(lineage);
1575-
if (!identityRecord) {
1576-
return null;
1577-
}
1578-
const sponsorChain = backend.db
1579-
.prepare<LineageMemberRow>(SELECT_IDENTITY_LINEAGE_MEMBERS_SQL)
1580-
.all(normalizedIdentityId)
1581-
.map((row) => normalizeOptionalString(row.principal_id))
1582-
.filter((value): value is string => Boolean(value));
1567+
// Keep identity, member, and token snapshots consistent when another
1568+
// connection issues a token while this lineage request is in flight.
1569+
backend.db.exec("BEGIN");
1570+
try {
1571+
const lineage = backend.db
1572+
.prepare<IdentityLineageRow>(SELECT_IDENTITY_LINEAGE_SQL)
1573+
.get(normalizedIdentityId);
1574+
if (!lineage) {
1575+
backend.db.exec("COMMIT");
1576+
return null;
1577+
}
15831578

1584-
const tokenMembers = backend.db
1585-
.prepare<TokenLineageMemberRow>(
1586-
SELECT_TOKEN_LINEAGE_MEMBERS_BY_IDENTITY_SQL,
1587-
)
1588-
.all(normalizedIdentityId, LINEAGE_TOKEN_QUERY_LIMIT)
1589-
.reduce((byToken, member) => {
1590-
const tokenId = normalizeOptionalString(member.token_id);
1591-
const principalId = normalizeOptionalString(member.principal_id);
1592-
if (tokenId && principalId) {
1593-
const members = byToken.get(tokenId) ?? [];
1594-
members.push(principalId);
1595-
byToken.set(tokenId, members);
1596-
}
1597-
return byToken;
1598-
}, new Map<string, string[]>());
1599-
1600-
const tokenRows = backend.db
1601-
.prepare<TokenLineageRow>(SELECT_TOKEN_LINEAGES_SQL)
1602-
.all(normalizedIdentityId, LINEAGE_TOKEN_QUERY_LIMIT);
1603-
const tokensTruncated = tokenRows.length > MAX_LINEAGE_TOKEN_RECORDS;
1604-
const tokens = tokenRows
1605-
.slice(0, MAX_LINEAGE_TOKEN_RECORDS)
1606-
.map((row) => {
1607-
const token = hydrateTokenLineage(row);
1608-
if (!token) {
1609-
return null;
1610-
}
1611-
return {
1612-
...token,
1613-
sponsorChain: [
1614-
...(tokenMembers.get(normalizeOptionalString(row.token_id) ?? "") ?? []),
1615-
],
1616-
};
1617-
})
1618-
.filter((token): token is TokenLineageRecord => token !== null);
1579+
const identityRecord = hydrateIdentityLineage(lineage);
1580+
if (!identityRecord) {
1581+
backend.db.exec("COMMIT");
1582+
return null;
1583+
}
1584+
const sponsorChain = backend.db
1585+
.prepare<LineageMemberRow>(SELECT_IDENTITY_LINEAGE_MEMBERS_SQL)
1586+
.all(normalizedIdentityId)
1587+
.map((row) => normalizeOptionalString(row.principal_id))
1588+
.filter((value): value is string => Boolean(value));
1589+
1590+
const tokenMembers = backend.db
1591+
.prepare<TokenLineageMemberRow>(
1592+
SELECT_TOKEN_LINEAGE_MEMBERS_BY_IDENTITY_SQL,
1593+
)
1594+
.all(normalizedIdentityId, LINEAGE_TOKEN_QUERY_LIMIT)
1595+
.reduce((byToken, member) => {
1596+
const tokenId = normalizeOptionalString(member.token_id);
1597+
const principalId = normalizeOptionalString(member.principal_id);
1598+
if (tokenId && principalId) {
1599+
const members = byToken.get(tokenId) ?? [];
1600+
members.push(principalId);
1601+
byToken.set(tokenId, members);
1602+
}
1603+
return byToken;
1604+
}, new Map<string, string[]>());
1605+
1606+
const tokenRows = backend.db
1607+
.prepare<TokenLineageRow>(SELECT_TOKEN_LINEAGES_SQL)
1608+
.all(normalizedIdentityId, LINEAGE_TOKEN_QUERY_LIMIT);
1609+
const tokensTruncated = tokenRows.length > MAX_LINEAGE_TOKEN_RECORDS;
1610+
const tokens = tokenRows
1611+
.slice(0, MAX_LINEAGE_TOKEN_RECORDS)
1612+
.map((row) => {
1613+
const token = hydrateTokenLineage(row);
1614+
if (!token) {
1615+
return null;
1616+
}
1617+
return {
1618+
...token,
1619+
sponsorChain: [
1620+
...(tokenMembers.get(normalizeOptionalString(row.token_id) ?? "") ?? []),
1621+
],
1622+
};
1623+
})
1624+
.filter((token): token is TokenLineageRecord => token !== null);
16191625

1620-
return {
1621-
...identityRecord,
1622-
sponsorChain,
1623-
tokens,
1624-
tokensTruncated,
1625-
};
1626+
const result = {
1627+
...identityRecord,
1628+
sponsorChain,
1629+
tokens,
1630+
tokensTruncated,
1631+
};
1632+
backend.db.exec("COMMIT");
1633+
return result;
1634+
} catch (error) {
1635+
try {
1636+
backend.db.exec("ROLLBACK");
1637+
} catch {
1638+
// Preserve the original query failure.
1639+
}
1640+
throw error;
1641+
}
16261642
}
16271643

16281644
private async getRequired(id: string): Promise<StoredIdentity> {

0 commit comments

Comments
 (0)