@tearleads/client-sdk 0.1.111 → 0.1.113
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Tearleads.js +0 -2
- package/Tearleads.js.map +1 -1
- package/client/containerContents/index.js +5 -1
- package/client/containerContents/index.js.map +1 -1
- package/client/deviceFirst/index.js +5 -1
- package/client/deviceFirst/index.js.map +1 -1
- package/client/identity/userIdentities.d.ts +13 -8
- package/client/identity/userIdentities.js +4 -22
- package/client/identity/userIdentities.js.map +1 -1
- package/client/index.d.ts +2 -2
- package/client/index.js +1 -0
- package/client/index.js.map +1 -1
- package/client/organizations/index.d.ts +1 -1
- package/client/organizations/index.js +12 -9
- package/client/organizations/index.js.map +1 -1
- package/client/organizations/organizationsTypes.d.ts +1 -2
- package/client/organizations/organizationsTypes.js.map +1 -1
- package/client/runtime/workflowRuntime.d.ts +1 -1
- package/client/runtime/workflowRuntime.js.map +1 -1
- package/data/persistence/keyingCheckpoints/keyingCheckpointAdvancePersistence.js +16 -11
- package/data/persistence/keyingCheckpoints/keyingCheckpointAdvancePersistence.js.map +1 -1
- package/data/persistence/keyingCheckpoints/keyingCheckpointPersistence.js +150 -2
- package/data/persistence/keyingCheckpoints/keyingCheckpointPersistence.js.map +1 -1
- package/data/persistence/keyingCheckpoints/principalAuthorizationCheckpoints.js +3 -2
- package/data/persistence/keyingCheckpoints/principalAuthorizationCheckpoints.js.map +1 -1
- package/data/persistence/principalHistory/principalHistoryEvidencePersistence.d.ts +14 -1
- package/data/persistence/principalHistory/principalHistoryEvidencePersistence.js +128 -34
- package/data/persistence/principalHistory/principalHistoryEvidencePersistence.js.map +1 -1
- package/data/persistence/principalHistory/principalHistoryNodeRetention.js +64 -24
- package/data/persistence/principalHistory/principalHistoryNodeRetention.js.map +1 -1
- package/data/persistence/principalHistory/principalHistoryPrefixPersistence.js +6 -1
- package/data/persistence/principalHistory/principalHistoryPrefixPersistence.js.map +1 -1
- package/data/persistence/principalHistory/principalHistoryRootOwnership.d.ts +45 -0
- package/data/persistence/principalHistory/principalHistoryRootOwnership.js +41 -4
- package/data/persistence/principalHistory/principalHistoryRootOwnership.js.map +1 -1
- package/data/persistence/principalHistory/principalHistoryStagePersistence.js +12 -8
- package/data/persistence/principalHistory/principalHistoryStagePersistence.js.map +1 -1
- package/data/persistence/principalKeys/principalGrantRetirementPersistence.js +35 -1
- package/data/persistence/principalKeys/principalGrantRetirementPersistence.js.map +1 -1
- package/data/persistence/principalPolicy/principalPolicyOrganizationPersistence.js +62 -1
- package/data/persistence/principalPolicy/principalPolicyOrganizationPersistence.js.map +1 -1
- package/data/persistence/trustedUserIdentityPinPersistence.js +70 -21
- package/data/persistence/trustedUserIdentityPinPersistence.js.map +1 -1
- package/data/persistence/verifiedManifestBundlePersistence.js +27 -10
- package/data/persistence/verifiedManifestBundlePersistence.js.map +1 -1
- package/data/sqlite/schema/index.d.ts +45 -5
- package/data/sqlite/schema/index.js +14 -6
- package/data/sqlite/schema/index.js.map +1 -1
- package/data/sqlite/schema/principalHistoryNodeRetentionSchema.d.ts +222 -0
- package/data/trustedUserIdentity/admission.d.ts +10 -0
- package/data/trustedUserIdentity/admission.js +15 -0
- package/data/trustedUserIdentity/admission.js.map +1 -0
- package/data/trustedUserIdentity/index.d.ts +1 -0
- package/data/trustedUserIdentity/index.js +1 -0
- package/data/trustedUserIdentity/index.js.map +1 -1
- package/data/trustedUserIdentity/service.d.ts +1 -5
- package/data/trustedUserIdentity/service.js +119 -46
- package/data/trustedUserIdentity/service.js.map +1 -1
- package/data/trustedUserIdentity/types.d.ts +12 -1
- package/data/trustedUserIdentity/types.js.map +1 -1
- package/index.d.ts +3 -3
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/stores/container-contents/containerContentsStore.js +10 -1
- package/stores/container-contents/containerContentsStore.js.map +1 -1
- package/workflows/container-contents/runtime.d.ts +7 -1
- package/workflows/container-contents/runtime.js +2 -1
- package/workflows/container-contents/runtime.js.map +1 -1
- package/workflows/organizations/index.d.ts +0 -1
- package/workflows/organizations/index.js +0 -1
- package/workflows/organizations/index.js.map +1 -1
- package/workflows/principals/historyRecovery/loadRecoveredPrincipalHistoryPage.js +13 -12
- package/workflows/principals/historyRecovery/loadRecoveredPrincipalHistoryPage.js.map +1 -1
- package/workflows/principals/historyRecovery/principalHistoryRecoveryReferences.js +17 -19
- package/workflows/principals/historyRecovery/principalHistoryRecoveryReferences.js.map +1 -1
- package/workflows/principals/publicHistory/selectPublicPrincipalHistory.js +8 -10
- package/workflows/principals/publicHistory/selectPublicPrincipalHistory.js.map +1 -1
- package/workflows/organizations/organizationUserImport.d.ts +0 -8
- package/workflows/organizations/organizationUserImport.js +0 -10
- package/workflows/organizations/organizationUserImport.js.map +0 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { KeyingVerificationError } from "../../../internal/crypto/index.js";
|
|
2
|
-
import { and, eq } from "drizzle-orm";
|
|
2
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
3
3
|
import { accessManifestCheckpoints, keyingCheckpointTables, principalPolicyCheckpoints, } from "../../sqlite/schema/index.js";
|
|
4
|
+
import { sqliteInsertBatchSize, sqliteRowsPerStatement, } from "../../sqlite/sqliteBindBudget.js";
|
|
4
5
|
import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
|
|
6
|
+
import { excludedValue } from "../../sqlite/sqliteUpsert.js";
|
|
5
7
|
import { ensureSqlTables } from "../../sqlite/sqlSchema.js";
|
|
6
|
-
import { recordPrincipalPolicyOrganizationInTransaction } from "../principalPolicy/principalPolicyOrganizationPersistence.js";
|
|
8
|
+
import { recordPrincipalPolicyOrganizationInTransaction, recordPrincipalPolicyOrganizationsInTransaction, } from "../principalPolicy/principalPolicyOrganizationPersistence.js";
|
|
7
9
|
/** One key format per checkpoint identity, shared by every batch validator. */
|
|
8
10
|
export function accessManifestObjectKey(identity) {
|
|
9
11
|
return JSON.stringify([
|
|
@@ -37,6 +39,80 @@ async function loadPrincipalPolicyCheckpointRow(handle, identity) {
|
|
|
37
39
|
.limit(1);
|
|
38
40
|
return rows[0] ?? null;
|
|
39
41
|
}
|
|
42
|
+
// An access lookup binds its kind and organization once; a policy lookup its type.
|
|
43
|
+
const ACCESS_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 2);
|
|
44
|
+
const POLICY_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);
|
|
45
|
+
const ACCESS_WRITE_BATCH_SIZE = sqliteInsertBatchSize(accessManifestCheckpoints);
|
|
46
|
+
const POLICY_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalPolicyCheckpoints);
|
|
47
|
+
/** Splits `values` into consecutive slices of at most `size`. */
|
|
48
|
+
function checkpointBatches(values, size) {
|
|
49
|
+
const result = [];
|
|
50
|
+
for (let index = 0; index < values.length; index += size)
|
|
51
|
+
result.push(values.slice(index, index + size));
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
/** Distinct ids per group key, in first-seen order. */
|
|
55
|
+
function groupIds(items, group, id) {
|
|
56
|
+
const groups = new Map();
|
|
57
|
+
for (const item of items) {
|
|
58
|
+
const entry = groups.get(group(item)) ?? { first: item, ids: [] };
|
|
59
|
+
if (!entry.ids.includes(id(item)))
|
|
60
|
+
entry.ids.push(id(item));
|
|
61
|
+
groups.set(group(item), entry);
|
|
62
|
+
}
|
|
63
|
+
return groups;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Every stored access checkpoint row for `identities`, keyed by
|
|
67
|
+
* `accessManifestObjectKey`: one read per object kind and organization.
|
|
68
|
+
*/
|
|
69
|
+
export async function loadAccessManifestCheckpointRows(handle, identities) {
|
|
70
|
+
const stored = new Map();
|
|
71
|
+
const groups = groupIds(identities, (identity) => JSON.stringify([identity.objectKind, identity.organizationId]), (identity) => identity.objectId);
|
|
72
|
+
for (const { first, ids } of groups.values())
|
|
73
|
+
for (const batch of checkpointBatches(ids, ACCESS_LOOKUP_BATCH_SIZE)) {
|
|
74
|
+
const rows = await handle
|
|
75
|
+
.select({
|
|
76
|
+
objectId: accessManifestCheckpoints.objectId,
|
|
77
|
+
epoch: accessManifestCheckpoints.epoch,
|
|
78
|
+
manifestHash: accessManifestCheckpoints.manifestHash,
|
|
79
|
+
})
|
|
80
|
+
.from(accessManifestCheckpoints)
|
|
81
|
+
.where(and(eq(accessManifestCheckpoints.objectKind, first.objectKind), eq(accessManifestCheckpoints.organizationId, first.organizationId), inArray(accessManifestCheckpoints.objectId, batch)));
|
|
82
|
+
for (const { objectId, ...row } of rows)
|
|
83
|
+
stored.set(accessManifestObjectKey({ ...first, objectId }), row);
|
|
84
|
+
}
|
|
85
|
+
return stored;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Every stored policy checkpoint for `identities`, keyed by
|
|
89
|
+
* `principalPolicyKey`: one read per principal type.
|
|
90
|
+
*/
|
|
91
|
+
export async function loadStoredPrincipalPolicyCheckpoints(handle, identities) {
|
|
92
|
+
const stored = new Map();
|
|
93
|
+
const groups = groupIds(identities, (identity) => identity.principalType, (identity) => identity.principalId);
|
|
94
|
+
for (const { first, ids } of groups.values())
|
|
95
|
+
for (const batch of checkpointBatches(ids, POLICY_LOOKUP_BATCH_SIZE)) {
|
|
96
|
+
const rows = await handle
|
|
97
|
+
.select({
|
|
98
|
+
principalId: principalPolicyCheckpoints.principalId,
|
|
99
|
+
version: principalPolicyCheckpoints.version,
|
|
100
|
+
stateHash: principalPolicyCheckpoints.stateHash,
|
|
101
|
+
})
|
|
102
|
+
.from(principalPolicyCheckpoints)
|
|
103
|
+
.where(and(eq(principalPolicyCheckpoints.principalType, first.principalType), inArray(principalPolicyCheckpoints.principalId, batch)));
|
|
104
|
+
for (const row of rows) {
|
|
105
|
+
const checkpoint = {
|
|
106
|
+
principalType: first.principalType,
|
|
107
|
+
principalId: row.principalId,
|
|
108
|
+
version: row.version,
|
|
109
|
+
stateHash: row.stateHash,
|
|
110
|
+
};
|
|
111
|
+
stored.set(principalPolicyKey(checkpoint), checkpoint);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return stored;
|
|
115
|
+
}
|
|
40
116
|
/**
|
|
41
117
|
* A loaded checkpoint row merged back with the identity it was looked up by —
|
|
42
118
|
* the full checkpoint shape every comparison consumes.
|
|
@@ -98,6 +174,78 @@ export async function upsertPrincipalPolicyCheckpointInTransaction(tx, checkpoin
|
|
|
98
174
|
})
|
|
99
175
|
.run();
|
|
100
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Upserts every checkpoint in multi-row statements; a repeated identity keeps
|
|
179
|
+
* its last checkpoint, as sequential upserts would.
|
|
180
|
+
*/
|
|
181
|
+
export async function upsertAccessManifestCheckpointsInTransaction(tx, checkpoints, updatedAt) {
|
|
182
|
+
const rows = new Map(checkpoints.map((checkpoint) => [
|
|
183
|
+
accessManifestObjectKey(checkpoint),
|
|
184
|
+
{
|
|
185
|
+
objectKind: checkpoint.objectKind,
|
|
186
|
+
organizationId: checkpoint.organizationId,
|
|
187
|
+
objectId: checkpoint.objectId,
|
|
188
|
+
epoch: checkpoint.epoch,
|
|
189
|
+
manifestHash: checkpoint.manifestHash,
|
|
190
|
+
updatedAt,
|
|
191
|
+
},
|
|
192
|
+
]));
|
|
193
|
+
for (const batch of checkpointBatches([...rows.values()], ACCESS_WRITE_BATCH_SIZE))
|
|
194
|
+
await tx
|
|
195
|
+
.insert(accessManifestCheckpoints)
|
|
196
|
+
.values(batch)
|
|
197
|
+
.onConflictDoUpdate({
|
|
198
|
+
target: [
|
|
199
|
+
accessManifestCheckpoints.objectKind,
|
|
200
|
+
accessManifestCheckpoints.organizationId,
|
|
201
|
+
accessManifestCheckpoints.objectId,
|
|
202
|
+
],
|
|
203
|
+
set: {
|
|
204
|
+
epoch: excludedValue(accessManifestCheckpoints.epoch),
|
|
205
|
+
manifestHash: excludedValue(accessManifestCheckpoints.manifestHash),
|
|
206
|
+
updatedAt: excludedValue(accessManifestCheckpoints.updatedAt),
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
.run();
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Records every checkpoint's cache ownership, failing on the first conflict in
|
|
213
|
+
* order as `upsertPrincipalPolicyCheckpointInTransaction` would, then upserts
|
|
214
|
+
* the checkpoints in multi-row statements.
|
|
215
|
+
*/
|
|
216
|
+
export async function upsertPrincipalPolicyCheckpointsInTransaction(tx, checkpoints, updatedAt, organizationId) {
|
|
217
|
+
await recordPrincipalPolicyOrganizationsInTransaction(tx, checkpoints.map((checkpoint) => ({
|
|
218
|
+
organizationId,
|
|
219
|
+
principalId: checkpoint.principalId,
|
|
220
|
+
principalType: checkpoint.principalType,
|
|
221
|
+
})));
|
|
222
|
+
const rows = new Map(checkpoints.map((checkpoint) => [
|
|
223
|
+
principalPolicyKey(checkpoint),
|
|
224
|
+
{
|
|
225
|
+
principalType: checkpoint.principalType,
|
|
226
|
+
principalId: checkpoint.principalId,
|
|
227
|
+
version: checkpoint.version,
|
|
228
|
+
stateHash: checkpoint.stateHash,
|
|
229
|
+
updatedAt,
|
|
230
|
+
},
|
|
231
|
+
]));
|
|
232
|
+
for (const batch of checkpointBatches([...rows.values()], POLICY_WRITE_BATCH_SIZE))
|
|
233
|
+
await tx
|
|
234
|
+
.insert(principalPolicyCheckpoints)
|
|
235
|
+
.values(batch)
|
|
236
|
+
.onConflictDoUpdate({
|
|
237
|
+
target: [
|
|
238
|
+
principalPolicyCheckpoints.principalType,
|
|
239
|
+
principalPolicyCheckpoints.principalId,
|
|
240
|
+
],
|
|
241
|
+
set: {
|
|
242
|
+
version: excludedValue(principalPolicyCheckpoints.version),
|
|
243
|
+
stateHash: excludedValue(principalPolicyCheckpoints.stateHash),
|
|
244
|
+
updatedAt: excludedValue(principalPolicyCheckpoints.updatedAt),
|
|
245
|
+
},
|
|
246
|
+
})
|
|
247
|
+
.run();
|
|
248
|
+
}
|
|
101
249
|
// Lazily create the checkpoint tables on first access. Every load/save below
|
|
102
250
|
// calls this, so the tables exist on demand; it is intentionally module-private
|
|
103
251
|
// (no caller outside this file needs it, and exporting it tripped knip).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyingCheckpointPersistence.js","sourceRoot":"","sources":["keyingCheckpointPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccessManifestCheckpoint,\n AccessObjectKind,\n ManagedPrincipalKind,\n PrincipalPolicyCheckpoint,\n} from \"@tearleads/crypto\";\nimport { KeyingVerificationError } from \"@tearleads/crypto\";\nimport { and, eq } from \"drizzle-orm\";\nimport {\n accessManifestCheckpoints,\n keyingCheckpointTables,\n principalPolicyCheckpoints,\n} from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteDatabase,\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { recordPrincipalPolicyOrganizationInTransaction } from \"../principalPolicy/principalPolicyOrganizationPersistence\";\n\ntype CheckpointHandle = ClientSQLiteDatabase | ClientSQLiteTransactionScope;\n\ntype AccessManifestCheckpointIdentity = Pick<\n AccessManifestCheckpoint,\n \"objectKind\" | \"organizationId\" | \"objectId\"\n>;\n\ntype PrincipalPolicyCheckpointIdentity = Pick<\n PrincipalPolicyCheckpoint,\n \"principalType\" | \"principalId\"\n>;\n\n/** One key format per checkpoint identity, shared by every batch validator. */\nexport function accessManifestObjectKey(\n identity: AccessManifestCheckpointIdentity,\n): string {\n return JSON.stringify([\n identity.objectKind,\n identity.organizationId,\n identity.objectId,\n ]);\n}\n\nexport function principalPolicyKey(\n identity: PrincipalPolicyCheckpointIdentity,\n): string {\n return JSON.stringify([identity.principalType, identity.principalId]);\n}\n\nasync function loadAccessManifestCheckpointRow(\n handle: CheckpointHandle,\n identity: AccessManifestCheckpointIdentity,\n): Promise<Pick<AccessManifestCheckpoint, \"epoch\" | \"manifestHash\"> | null> {\n const rows = await handle\n .select({\n epoch: accessManifestCheckpoints.epoch,\n manifestHash: accessManifestCheckpoints.manifestHash,\n })\n .from(accessManifestCheckpoints)\n .where(\n and(\n eq(accessManifestCheckpoints.objectKind, identity.objectKind),\n eq(accessManifestCheckpoints.organizationId, identity.organizationId),\n eq(accessManifestCheckpoints.objectId, identity.objectId),\n ),\n )\n .limit(1);\n return rows[0] ?? null;\n}\n\nasync function loadPrincipalPolicyCheckpointRow(\n handle: CheckpointHandle,\n identity: PrincipalPolicyCheckpointIdentity,\n): Promise<Pick<PrincipalPolicyCheckpoint, \"version\" | \"stateHash\"> | null> {\n const rows = await handle\n .select({\n version: principalPolicyCheckpoints.version,\n stateHash: principalPolicyCheckpoints.stateHash,\n })\n .from(principalPolicyCheckpoints)\n .where(\n and(\n eq(principalPolicyCheckpoints.principalType, identity.principalType),\n eq(principalPolicyCheckpoints.principalId, identity.principalId),\n ),\n )\n .limit(1);\n return rows[0] ?? null;\n}\n\n/**\n * A loaded checkpoint row merged back with the identity it was looked up by —\n * the full checkpoint shape every comparison consumes.\n */\nexport async function loadStoredAccessManifestCheckpoint(\n handle: CheckpointHandle,\n checkpoint: AccessManifestCheckpoint,\n): Promise<AccessManifestCheckpoint | null> {\n const row = await loadAccessManifestCheckpointRow(handle, checkpoint);\n return row ? { ...checkpoint, ...row } : null;\n}\n\nexport async function loadStoredPrincipalPolicyCheckpoint(\n handle: CheckpointHandle,\n identity: PrincipalPolicyCheckpointIdentity,\n): Promise<PrincipalPolicyCheckpoint | null> {\n const row = await loadPrincipalPolicyCheckpointRow(handle, identity);\n return row\n ? {\n principalType: identity.principalType,\n principalId: identity.principalId,\n ...row,\n }\n : null;\n}\n\n/** Every principal with a durable policy checkpoint. */\nexport function listPrincipalPolicyCheckpointPrincipals(\n handle: CheckpointHandle,\n): Promise<{ id: string; type: string }[]> {\n return handle\n .selectDistinct({\n id: principalPolicyCheckpoints.principalId,\n type: principalPolicyCheckpoints.principalType,\n })\n .from(principalPolicyCheckpoints);\n}\n\nexport async function upsertAccessManifestCheckpointInTransaction(\n tx: ClientSQLiteTransactionScope,\n checkpoint: AccessManifestCheckpoint,\n updatedAt: string,\n): Promise<void> {\n const row = { ...checkpoint, updatedAt };\n await tx\n .insert(accessManifestCheckpoints)\n .values(row)\n .onConflictDoUpdate({\n target: [\n accessManifestCheckpoints.objectKind,\n accessManifestCheckpoints.organizationId,\n accessManifestCheckpoints.objectId,\n ],\n set: row,\n })\n .run();\n}\n\nexport async function upsertPrincipalPolicyCheckpointInTransaction(\n tx: ClientSQLiteTransactionScope,\n checkpoint: PrincipalPolicyCheckpoint,\n updatedAt: string,\n organizationId?: string | undefined,\n): Promise<void> {\n await recordPrincipalPolicyOrganizationInTransaction(tx, {\n organizationId,\n principalId: checkpoint.principalId,\n principalType: checkpoint.principalType,\n });\n const row = { ...checkpoint, updatedAt };\n await tx\n .insert(principalPolicyCheckpoints)\n .values(row)\n .onConflictDoUpdate({\n target: [\n principalPolicyCheckpoints.principalType,\n principalPolicyCheckpoints.principalId,\n ],\n set: row,\n })\n .run();\n}\n\n// Lazily create the checkpoint tables on first access. Every load/save below\n// calls this, so the tables exist on demand; it is intentionally module-private\n// (no caller outside this file needs it, and exporting it tripped knip).\nasync function ensureKeyingCheckpointTables(execSql: ExecSql): Promise<void> {\n await ensureSqlTables(execSql, keyingCheckpointTables);\n}\n\nexport async function loadAccessManifestCheckpoint(\n execSql: ExecSql,\n objectKind: AccessObjectKind,\n organizationId: string,\n objectId: string,\n): Promise<AccessManifestCheckpoint | null> {\n await ensureKeyingCheckpointTables(execSql);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const row = await loadAccessManifestCheckpointRow(db, {\n objectKind,\n organizationId,\n objectId,\n });\n\n return row ? { objectKind, objectId, organizationId, ...row } : null;\n}\n\nexport async function loadPrincipalPolicyCheckpoint(\n execSql: ExecSql,\n principalType: ManagedPrincipalKind,\n principalId: string,\n): Promise<PrincipalPolicyCheckpoint | null> {\n await ensureKeyingCheckpointTables(execSql);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const row = await loadPrincipalPolicyCheckpointRow(db, {\n principalType,\n principalId,\n });\n\n return row ? { principalType, principalId, ...row } : null;\n}\n/**\n * The shared shape/ownership guard every checkpoint comparison runs before\n * trusting a durable pin: the pin must belong to the compared principal and\n * carry a well-formed version and hash.\n */\nexport function assertPrincipalPolicyCheckpointShape(\n checkpoint: PrincipalPolicyCheckpoint,\n head: PrincipalPolicyCheckpointIdentity,\n): void {\n if (\n checkpoint.principalType !== head.principalType ||\n checkpoint.principalId !== head.principalId\n ) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"principal policy checkpoint belongs to another principal\",\n );\n }\n if (\n !Number.isInteger(checkpoint.version) ||\n checkpoint.version < 1 ||\n checkpoint.stateHash.length === 0\n ) {\n throw new KeyingVerificationError(\n \"invalid_shape\",\n \"principal policy checkpoint is malformed\",\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"keyingCheckpointPersistence.js","sourceRoot":"","sources":["keyingCheckpointPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccessManifestCheckpoint,\n AccessObjectKind,\n ManagedPrincipalKind,\n PrincipalPolicyCheckpoint,\n} from \"@tearleads/crypto\";\nimport { KeyingVerificationError } from \"@tearleads/crypto\";\nimport { and, eq, inArray } from \"drizzle-orm\";\nimport {\n accessManifestCheckpoints,\n keyingCheckpointTables,\n principalPolicyCheckpoints,\n} from \"../../sqlite/schema\";\nimport {\n sqliteInsertBatchSize,\n sqliteRowsPerStatement,\n} from \"../../sqlite/sqliteBindBudget\";\nimport {\n type ClientSQLiteDatabase,\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { excludedValue } from \"../../sqlite/sqliteUpsert\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport {\n recordPrincipalPolicyOrganizationInTransaction,\n recordPrincipalPolicyOrganizationsInTransaction,\n} from \"../principalPolicy/principalPolicyOrganizationPersistence\";\n\ntype CheckpointHandle = ClientSQLiteDatabase | ClientSQLiteTransactionScope;\n\ntype AccessManifestCheckpointIdentity = Pick<\n AccessManifestCheckpoint,\n \"objectKind\" | \"organizationId\" | \"objectId\"\n>;\n\ntype PrincipalPolicyCheckpointIdentity = Pick<\n PrincipalPolicyCheckpoint,\n \"principalType\" | \"principalId\"\n>;\n\n/** One key format per checkpoint identity, shared by every batch validator. */\nexport function accessManifestObjectKey(\n identity: AccessManifestCheckpointIdentity,\n): string {\n return JSON.stringify([\n identity.objectKind,\n identity.organizationId,\n identity.objectId,\n ]);\n}\n\nexport function principalPolicyKey(\n identity: PrincipalPolicyCheckpointIdentity,\n): string {\n return JSON.stringify([identity.principalType, identity.principalId]);\n}\n\nasync function loadAccessManifestCheckpointRow(\n handle: CheckpointHandle,\n identity: AccessManifestCheckpointIdentity,\n): Promise<Pick<AccessManifestCheckpoint, \"epoch\" | \"manifestHash\"> | null> {\n const rows = await handle\n .select({\n epoch: accessManifestCheckpoints.epoch,\n manifestHash: accessManifestCheckpoints.manifestHash,\n })\n .from(accessManifestCheckpoints)\n .where(\n and(\n eq(accessManifestCheckpoints.objectKind, identity.objectKind),\n eq(accessManifestCheckpoints.organizationId, identity.organizationId),\n eq(accessManifestCheckpoints.objectId, identity.objectId),\n ),\n )\n .limit(1);\n return rows[0] ?? null;\n}\n\nasync function loadPrincipalPolicyCheckpointRow(\n handle: CheckpointHandle,\n identity: PrincipalPolicyCheckpointIdentity,\n): Promise<Pick<PrincipalPolicyCheckpoint, \"version\" | \"stateHash\"> | null> {\n const rows = await handle\n .select({\n version: principalPolicyCheckpoints.version,\n stateHash: principalPolicyCheckpoints.stateHash,\n })\n .from(principalPolicyCheckpoints)\n .where(\n and(\n eq(principalPolicyCheckpoints.principalType, identity.principalType),\n eq(principalPolicyCheckpoints.principalId, identity.principalId),\n ),\n )\n .limit(1);\n return rows[0] ?? null;\n}\n\n// An access lookup binds its kind and organization once; a policy lookup its type.\nconst ACCESS_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 2);\nconst POLICY_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);\nconst ACCESS_WRITE_BATCH_SIZE = sqliteInsertBatchSize(\n accessManifestCheckpoints,\n);\nconst POLICY_WRITE_BATCH_SIZE = sqliteInsertBatchSize(\n principalPolicyCheckpoints,\n);\n\n/** Splits `values` into consecutive slices of at most `size`. */\nfunction checkpointBatches<T>(values: readonly T[], size: number): T[][] {\n const result: T[][] = [];\n for (let index = 0; index < values.length; index += size)\n result.push(values.slice(index, index + size));\n return result;\n}\n\n/** Distinct ids per group key, in first-seen order. */\nfunction groupIds<T>(\n items: readonly T[],\n group: (item: T) => string,\n id: (item: T) => string,\n): Map<string, { readonly first: T; readonly ids: string[] }> {\n const groups = new Map<string, { first: T; ids: string[] }>();\n for (const item of items) {\n const entry = groups.get(group(item)) ?? { first: item, ids: [] };\n if (!entry.ids.includes(id(item))) entry.ids.push(id(item));\n groups.set(group(item), entry);\n }\n return groups;\n}\n\n/**\n * Every stored access checkpoint row for `identities`, keyed by\n * `accessManifestObjectKey`: one read per object kind and organization.\n */\nexport async function loadAccessManifestCheckpointRows(\n handle: CheckpointHandle,\n identities: readonly AccessManifestCheckpointIdentity[],\n): Promise<\n Map<string, Pick<AccessManifestCheckpoint, \"epoch\" | \"manifestHash\">>\n> {\n const stored = new Map<\n string,\n Pick<AccessManifestCheckpoint, \"epoch\" | \"manifestHash\">\n >();\n const groups = groupIds(\n identities,\n (identity) =>\n JSON.stringify([identity.objectKind, identity.organizationId]),\n (identity) => identity.objectId,\n );\n for (const { first, ids } of groups.values())\n for (const batch of checkpointBatches(ids, ACCESS_LOOKUP_BATCH_SIZE)) {\n const rows = await handle\n .select({\n objectId: accessManifestCheckpoints.objectId,\n epoch: accessManifestCheckpoints.epoch,\n manifestHash: accessManifestCheckpoints.manifestHash,\n })\n .from(accessManifestCheckpoints)\n .where(\n and(\n eq(accessManifestCheckpoints.objectKind, first.objectKind),\n eq(accessManifestCheckpoints.organizationId, first.organizationId),\n inArray(accessManifestCheckpoints.objectId, batch),\n ),\n );\n for (const { objectId, ...row } of rows)\n stored.set(accessManifestObjectKey({ ...first, objectId }), row);\n }\n return stored;\n}\n\n/**\n * Every stored policy checkpoint for `identities`, keyed by\n * `principalPolicyKey`: one read per principal type.\n */\nexport async function loadStoredPrincipalPolicyCheckpoints(\n handle: CheckpointHandle,\n identities: readonly PrincipalPolicyCheckpointIdentity[],\n): Promise<Map<string, PrincipalPolicyCheckpoint>> {\n const stored = new Map<string, PrincipalPolicyCheckpoint>();\n const groups = groupIds(\n identities,\n (identity) => identity.principalType,\n (identity) => identity.principalId,\n );\n for (const { first, ids } of groups.values())\n for (const batch of checkpointBatches(ids, POLICY_LOOKUP_BATCH_SIZE)) {\n const rows = await handle\n .select({\n principalId: principalPolicyCheckpoints.principalId,\n version: principalPolicyCheckpoints.version,\n stateHash: principalPolicyCheckpoints.stateHash,\n })\n .from(principalPolicyCheckpoints)\n .where(\n and(\n eq(principalPolicyCheckpoints.principalType, first.principalType),\n inArray(principalPolicyCheckpoints.principalId, batch),\n ),\n );\n for (const row of rows) {\n const checkpoint = {\n principalType: first.principalType,\n principalId: row.principalId,\n version: row.version,\n stateHash: row.stateHash,\n };\n stored.set(principalPolicyKey(checkpoint), checkpoint);\n }\n }\n return stored;\n}\n\n/**\n * A loaded checkpoint row merged back with the identity it was looked up by —\n * the full checkpoint shape every comparison consumes.\n */\nexport async function loadStoredAccessManifestCheckpoint(\n handle: CheckpointHandle,\n checkpoint: AccessManifestCheckpoint,\n): Promise<AccessManifestCheckpoint | null> {\n const row = await loadAccessManifestCheckpointRow(handle, checkpoint);\n return row ? { ...checkpoint, ...row } : null;\n}\n\nexport async function loadStoredPrincipalPolicyCheckpoint(\n handle: CheckpointHandle,\n identity: PrincipalPolicyCheckpointIdentity,\n): Promise<PrincipalPolicyCheckpoint | null> {\n const row = await loadPrincipalPolicyCheckpointRow(handle, identity);\n return row\n ? {\n principalType: identity.principalType,\n principalId: identity.principalId,\n ...row,\n }\n : null;\n}\n\n/** Every principal with a durable policy checkpoint. */\nexport function listPrincipalPolicyCheckpointPrincipals(\n handle: CheckpointHandle,\n): Promise<{ id: string; type: string }[]> {\n return handle\n .selectDistinct({\n id: principalPolicyCheckpoints.principalId,\n type: principalPolicyCheckpoints.principalType,\n })\n .from(principalPolicyCheckpoints);\n}\n\nexport async function upsertAccessManifestCheckpointInTransaction(\n tx: ClientSQLiteTransactionScope,\n checkpoint: AccessManifestCheckpoint,\n updatedAt: string,\n): Promise<void> {\n const row = { ...checkpoint, updatedAt };\n await tx\n .insert(accessManifestCheckpoints)\n .values(row)\n .onConflictDoUpdate({\n target: [\n accessManifestCheckpoints.objectKind,\n accessManifestCheckpoints.organizationId,\n accessManifestCheckpoints.objectId,\n ],\n set: row,\n })\n .run();\n}\n\nexport async function upsertPrincipalPolicyCheckpointInTransaction(\n tx: ClientSQLiteTransactionScope,\n checkpoint: PrincipalPolicyCheckpoint,\n updatedAt: string,\n organizationId?: string | undefined,\n): Promise<void> {\n await recordPrincipalPolicyOrganizationInTransaction(tx, {\n organizationId,\n principalId: checkpoint.principalId,\n principalType: checkpoint.principalType,\n });\n const row = { ...checkpoint, updatedAt };\n await tx\n .insert(principalPolicyCheckpoints)\n .values(row)\n .onConflictDoUpdate({\n target: [\n principalPolicyCheckpoints.principalType,\n principalPolicyCheckpoints.principalId,\n ],\n set: row,\n })\n .run();\n}\n\n/**\n * Upserts every checkpoint in multi-row statements; a repeated identity keeps\n * its last checkpoint, as sequential upserts would.\n */\nexport async function upsertAccessManifestCheckpointsInTransaction(\n tx: ClientSQLiteTransactionScope,\n checkpoints: readonly AccessManifestCheckpoint[],\n updatedAt: string,\n): Promise<void> {\n const rows = new Map(\n checkpoints.map((checkpoint) => [\n accessManifestObjectKey(checkpoint),\n {\n objectKind: checkpoint.objectKind,\n organizationId: checkpoint.organizationId,\n objectId: checkpoint.objectId,\n epoch: checkpoint.epoch,\n manifestHash: checkpoint.manifestHash,\n updatedAt,\n },\n ]),\n );\n for (const batch of checkpointBatches(\n [...rows.values()],\n ACCESS_WRITE_BATCH_SIZE,\n ))\n await tx\n .insert(accessManifestCheckpoints)\n .values(batch)\n .onConflictDoUpdate({\n target: [\n accessManifestCheckpoints.objectKind,\n accessManifestCheckpoints.organizationId,\n accessManifestCheckpoints.objectId,\n ],\n set: {\n epoch: excludedValue(accessManifestCheckpoints.epoch),\n manifestHash: excludedValue(accessManifestCheckpoints.manifestHash),\n updatedAt: excludedValue(accessManifestCheckpoints.updatedAt),\n },\n })\n .run();\n}\n\n/**\n * Records every checkpoint's cache ownership, failing on the first conflict in\n * order as `upsertPrincipalPolicyCheckpointInTransaction` would, then upserts\n * the checkpoints in multi-row statements.\n */\nexport async function upsertPrincipalPolicyCheckpointsInTransaction(\n tx: ClientSQLiteTransactionScope,\n checkpoints: readonly PrincipalPolicyCheckpoint[],\n updatedAt: string,\n organizationId?: string | undefined,\n): Promise<void> {\n await recordPrincipalPolicyOrganizationsInTransaction(\n tx,\n checkpoints.map((checkpoint) => ({\n organizationId,\n principalId: checkpoint.principalId,\n principalType: checkpoint.principalType,\n })),\n );\n const rows = new Map(\n checkpoints.map((checkpoint) => [\n principalPolicyKey(checkpoint),\n {\n principalType: checkpoint.principalType,\n principalId: checkpoint.principalId,\n version: checkpoint.version,\n stateHash: checkpoint.stateHash,\n updatedAt,\n },\n ]),\n );\n for (const batch of checkpointBatches(\n [...rows.values()],\n POLICY_WRITE_BATCH_SIZE,\n ))\n await tx\n .insert(principalPolicyCheckpoints)\n .values(batch)\n .onConflictDoUpdate({\n target: [\n principalPolicyCheckpoints.principalType,\n principalPolicyCheckpoints.principalId,\n ],\n set: {\n version: excludedValue(principalPolicyCheckpoints.version),\n stateHash: excludedValue(principalPolicyCheckpoints.stateHash),\n updatedAt: excludedValue(principalPolicyCheckpoints.updatedAt),\n },\n })\n .run();\n}\n\n// Lazily create the checkpoint tables on first access. Every load/save below\n// calls this, so the tables exist on demand; it is intentionally module-private\n// (no caller outside this file needs it, and exporting it tripped knip).\nasync function ensureKeyingCheckpointTables(execSql: ExecSql): Promise<void> {\n await ensureSqlTables(execSql, keyingCheckpointTables);\n}\n\nexport async function loadAccessManifestCheckpoint(\n execSql: ExecSql,\n objectKind: AccessObjectKind,\n organizationId: string,\n objectId: string,\n): Promise<AccessManifestCheckpoint | null> {\n await ensureKeyingCheckpointTables(execSql);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const row = await loadAccessManifestCheckpointRow(db, {\n objectKind,\n organizationId,\n objectId,\n });\n\n return row ? { objectKind, objectId, organizationId, ...row } : null;\n}\n\nexport async function loadPrincipalPolicyCheckpoint(\n execSql: ExecSql,\n principalType: ManagedPrincipalKind,\n principalId: string,\n): Promise<PrincipalPolicyCheckpoint | null> {\n await ensureKeyingCheckpointTables(execSql);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const row = await loadPrincipalPolicyCheckpointRow(db, {\n principalType,\n principalId,\n });\n\n return row ? { principalType, principalId, ...row } : null;\n}\n/**\n * The shared shape/ownership guard every checkpoint comparison runs before\n * trusting a durable pin: the pin must belong to the compared principal and\n * carry a well-formed version and hash.\n */\nexport function assertPrincipalPolicyCheckpointShape(\n checkpoint: PrincipalPolicyCheckpoint,\n head: PrincipalPolicyCheckpointIdentity,\n): void {\n if (\n checkpoint.principalType !== head.principalType ||\n checkpoint.principalId !== head.principalId\n ) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"principal policy checkpoint belongs to another principal\",\n );\n }\n if (\n !Number.isInteger(checkpoint.version) ||\n checkpoint.version < 1 ||\n checkpoint.stateHash.length === 0\n ) {\n throw new KeyingVerificationError(\n \"invalid_shape\",\n \"principal policy checkpoint is malformed\",\n );\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { KeyingVerificationError, verifyPrincipalPolicyCheckpoint, } from "../../../internal/crypto/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { loadStoredPrincipalPolicyCheckpoints, principalPolicyKey, } from "./keyingCheckpointPersistence.js";
|
|
3
3
|
export class PrincipalAuthorizationCheckpointUnavailableError extends KeyingVerificationError {
|
|
4
4
|
constructor() {
|
|
5
5
|
super("missing_dependency", "Public authorization cannot connect to the latest durable checkpoint");
|
|
@@ -7,8 +7,9 @@ export class PrincipalAuthorizationCheckpointUnavailableError extends KeyingVeri
|
|
|
7
7
|
}
|
|
8
8
|
/** Validate public evidence or a verified private ancestry bridge without admitting pins. */
|
|
9
9
|
export async function validatePrincipalAuthorizationCheckpoints(tx, policies) {
|
|
10
|
+
const stored = await loadStoredPrincipalPolicyCheckpoints(tx, policies);
|
|
10
11
|
for (const policy of policies) {
|
|
11
|
-
const checkpoint =
|
|
12
|
+
const checkpoint = stored.get(principalPolicyKey(policy));
|
|
12
13
|
if (!checkpoint)
|
|
13
14
|
continue;
|
|
14
15
|
const matchingHead = policies.filter((other) => other.principalType === policy.principalType &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"principalAuthorizationCheckpoints.js","sourceRoot":"","sources":["principalAuthorizationCheckpoints.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EAGvB,+BAA+B,GAChC,MAAM,mBAAmB,CAAC;AAE3B,OAAO,
|
|
1
|
+
{"version":3,"file":"principalAuthorizationCheckpoints.js","sourceRoot":"","sources":["principalAuthorizationCheckpoints.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EAGvB,+BAA+B,GAChC,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,oCAAoC,EACpC,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AAEvC,MAAM,OAAO,gDAAiD,SAAQ,uBAAuB;IAC3F;QACE,KAAK,CACH,oBAAoB,EACpB,sEAAsE,CACvE,CAAC;IACJ,CAAC;CACF;AAED,6FAA6F;AAC7F,MAAM,CAAC,KAAK,UAAU,yCAAyC,CAC7D,EAAgC,EAChC,QAGG;IAEH,MAAM,MAAM,GAAG,MAAM,oCAAoC,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IACxE,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1D,IAAI,CAAC,UAAU;YAAE,SAAS;QAC1B,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAClC,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,aAAa,KAAK,MAAM,CAAC,aAAa;YAC5C,KAAK,CAAC,WAAW,KAAK,MAAM,CAAC,WAAW;YACxC,KAAK,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,CACvC,CAAC;QACF,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QACrE,IACE,UAAU,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO;YACnC,CAAC,UAAU,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO;gBAClC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,UAAU,CAAC,OAAO,CAAC,CAAC;YAEnE,MAAM,IAAI,gDAAgD,EAAE,CAAC;QAC/D,+BAA+B,CAAC;YAC9B,KAAK;YACL,YAAY,EAAE,MAAM,CAAC,KAAK;YAC1B,eAAe,EAAE,UAAU;SAC5B,CAAC,CAAC;IACL,CAAC;AACH,CAAC","sourcesContent":["import {\n KeyingVerificationError,\n type VerifiedPrincipalPolicyCurrent,\n type VerifiedPrincipalPolicySelection,\n verifyPrincipalPolicyCheckpoint,\n} from \"@tearleads/crypto\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n loadStoredPrincipalPolicyCheckpoints,\n principalPolicyKey,\n} from \"./keyingCheckpointPersistence\";\n\nexport class PrincipalAuthorizationCheckpointUnavailableError extends KeyingVerificationError {\n constructor() {\n super(\n \"missing_dependency\",\n \"Public authorization cannot connect to the latest durable checkpoint\",\n );\n }\n}\n\n/** Validate public evidence or a verified private ancestry bridge without admitting pins. */\nexport async function validatePrincipalAuthorizationCheckpoints(\n tx: ClientSQLiteTransactionScope,\n policies: readonly (\n | VerifiedPrincipalPolicySelection\n | VerifiedPrincipalPolicyCurrent\n )[],\n): Promise<void> {\n const stored = await loadStoredPrincipalPolicyCheckpoints(tx, policies);\n for (const policy of policies) {\n const checkpoint = stored.get(principalPolicyKey(policy));\n if (!checkpoint) continue;\n const matchingHead = policies.filter(\n (other) =>\n other.principalType === policy.principalType &&\n other.principalId === policy.principalId &&\n other.stateHash === policy.stateHash,\n );\n const chain = matchingHead.flatMap((other) => other.retainedHistory);\n if (\n checkpoint.version > policy.version ||\n (checkpoint.version < policy.version &&\n !chain.some(({ state }) => state.version === checkpoint.version))\n )\n throw new PrincipalAuthorizationCheckpointUnavailableError();\n verifyPrincipalPolicyCheckpoint({\n chain,\n currentState: policy.state,\n localCheckpoint: checkpoint,\n });\n }\n}\n"]}
|
|
@@ -2,6 +2,7 @@ import { type PrincipalHistoryIndexNode, type PrincipalPolicyHistoryReferencePro
|
|
|
2
2
|
import { type PrincipalPolicyStateChainEntryResponse } from "../../../internal/validators/response/index.js";
|
|
3
3
|
import { type ClientSQLiteTransactionScope } from "../../sqlite/sqlitePersistenceRuntime.js";
|
|
4
4
|
import { type ExecSql } from "../../sqlite/sqlSchema.js";
|
|
5
|
+
import type { PrincipalHistoryRootEdges } from "./principalHistoryRootOwnership.js";
|
|
5
6
|
export interface PrincipalHistoryEvidencePage {
|
|
6
7
|
readonly indexRootHash: string;
|
|
7
8
|
readonly scopeId: string;
|
|
@@ -21,7 +22,19 @@ export declare function preparePrincipalHistoryEvidencePage(input: {
|
|
|
21
22
|
readonly nodes: readonly PrincipalHistoryIndexNode[];
|
|
22
23
|
}): Promise<PrincipalHistoryEvidencePage>;
|
|
23
24
|
/** Publish in the same guarded transaction as the accepted page's progress. */
|
|
24
|
-
export declare function writePrincipalHistoryEvidencePage(tx: ClientSQLiteTransactionScope, page: PrincipalHistoryEvidencePage): Promise<void>;
|
|
25
|
+
export declare function writePrincipalHistoryEvidencePage(tx: ClientSQLiteTransactionScope, page: PrincipalHistoryEvidencePage, rootEdges?: PrincipalHistoryRootEdges | undefined): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Returns untrusted material, in `versions` order. The caller must verify it
|
|
28
|
+
* against the issued root. Index nodes are read one level at a time for every
|
|
29
|
+
* version and entries in one set read; the first version that fails, in
|
|
30
|
+
* order, throws the error loading it alone would.
|
|
31
|
+
*/
|
|
32
|
+
export declare function loadPrincipalHistoryReferences(input: {
|
|
33
|
+
readonly execSql: ExecSql;
|
|
34
|
+
readonly scopeId: string;
|
|
35
|
+
readonly history: VerifiedPrincipalPolicyHistory;
|
|
36
|
+
readonly versions: readonly number[];
|
|
37
|
+
}): Promise<PrincipalPolicyHistoryReferenceProof[]>;
|
|
25
38
|
/** Returns untrusted material. The caller must verify it against the issued root. */
|
|
26
39
|
export declare function loadPrincipalHistoryReference(input: {
|
|
27
40
|
readonly execSql: ExecSql;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { compareCanonicalStrings, KeyingVerificationError, PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT, principalHistoryIndexLeaf, resolvePrincipalHistoryIndexProof, } from "../../../internal/crypto/index.js";
|
|
2
2
|
import { isPrincipalPolicyStateChainEntryResponse, } from "../../../internal/validators/response/index.js";
|
|
3
|
-
import { and, eq } from "drizzle-orm";
|
|
3
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
4
4
|
import { principalHistoryEntries, principalHistoryEvidenceTables, principalHistoryNodes, } from "../../sqlite/schema/principalHistoryEvidenceSchema.js";
|
|
5
|
-
import { sqliteInsertBatchSize } from "../../sqlite/sqliteBindBudget.js";
|
|
5
|
+
import { sqliteInsertBatchSize, sqliteRowsPerStatement, } from "../../sqlite/sqliteBindBudget.js";
|
|
6
6
|
import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
|
|
7
7
|
import { excludedValue } from "../../sqlite/sqliteUpsert.js";
|
|
8
8
|
import { ensureSqlTables } from "../../sqlite/sqlSchema.js";
|
|
@@ -23,7 +23,7 @@ export async function preparePrincipalHistoryEvidencePage(input) {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
/** Publish in the same guarded transaction as the accepted page's progress. */
|
|
26
|
-
export async function writePrincipalHistoryEvidencePage(tx, page) {
|
|
26
|
+
export async function writePrincipalHistoryEvidencePage(tx, page, rootEdges) {
|
|
27
27
|
const scope = { scopeId: page.scopeId, organizationId: page.organizationId };
|
|
28
28
|
// A repeated leaf keeps its last entry, as sequential upserts would.
|
|
29
29
|
const entries = new Map(page.entries.map((entry) => [
|
|
@@ -46,47 +46,141 @@ export async function writePrincipalHistoryEvidencePage(tx, page) {
|
|
|
46
46
|
},
|
|
47
47
|
})
|
|
48
48
|
.run();
|
|
49
|
-
await writePrincipalHistoryNodes(tx, scope, [...page.nodes].sort((a, b) => compareCanonicalStrings(a.hash, b.hash)));
|
|
49
|
+
await writePrincipalHistoryNodes(tx, scope, [...page.nodes].sort((a, b) => compareCanonicalStrings(a.hash, b.hash)), rootEdges);
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
// The scope id binds once per lookup.
|
|
52
|
+
const HASH_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);
|
|
53
|
+
/** Thrown by the preloaded node reader to ask the next level's set read for a hash. */
|
|
54
|
+
class PrincipalHistoryNodeNotLoaded {
|
|
55
|
+
hash;
|
|
56
|
+
constructor(hash) {
|
|
57
|
+
this.hash = hash;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function hashBatches(hashes) {
|
|
61
|
+
const result = [];
|
|
62
|
+
for (let index = 0; index < hashes.length; index += HASH_LOOKUP_BATCH_SIZE)
|
|
63
|
+
result.push(hashes.slice(index, index + HASH_LOOKUP_BATCH_SIZE));
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolves every version's index path level by level: each pass walks every
|
|
68
|
+
* unfinished version over the nodes read so far, and one set read per level
|
|
69
|
+
* supplies the nodes the walks stopped at. A node absent after its read stays
|
|
70
|
+
* missing, so the unchanged verifier fails that version as it would serially.
|
|
71
|
+
*/
|
|
72
|
+
async function resolvePrincipalHistoryProofs(db, input) {
|
|
73
|
+
const nodes = new Map();
|
|
74
|
+
const results = [];
|
|
75
|
+
let pending = input.versions.map((_, index) => index);
|
|
76
|
+
while (pending.length > 0) {
|
|
77
|
+
const needed = new Set();
|
|
78
|
+
const unfinished = [];
|
|
79
|
+
for (const index of pending) {
|
|
80
|
+
try {
|
|
81
|
+
results[index] = {
|
|
82
|
+
value: await resolvePrincipalHistoryIndexProof({
|
|
83
|
+
rootHash: input.history.indexRootHash,
|
|
84
|
+
treeSize: input.history.currentEntry.state.version,
|
|
85
|
+
version: input.versions[index] ?? 0,
|
|
86
|
+
readNode: async (hash) => {
|
|
87
|
+
const node = nodes.get(hash);
|
|
88
|
+
if (node === undefined)
|
|
89
|
+
throw new PrincipalHistoryNodeNotLoaded(hash);
|
|
90
|
+
return node;
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (!(error instanceof PrincipalHistoryNodeNotLoaded)) {
|
|
97
|
+
results[index] = { error };
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
needed.add(error.hash);
|
|
101
|
+
unfinished.push(index);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const batch of hashBatches([...needed])) {
|
|
105
|
+
for (const hash of batch)
|
|
106
|
+
nodes.set(hash, null);
|
|
107
|
+
const rows = await db
|
|
61
108
|
.select()
|
|
62
109
|
.from(principalHistoryNodes)
|
|
63
|
-
.where(and(eq(principalHistoryNodes.scopeId, input.scopeId),
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
110
|
+
.where(and(eq(principalHistoryNodes.scopeId, input.scopeId), inArray(principalHistoryNodes.hash, batch)));
|
|
111
|
+
for (const row of rows)
|
|
112
|
+
nodes.set(row.hash, row);
|
|
113
|
+
}
|
|
114
|
+
pending = unfinished;
|
|
115
|
+
}
|
|
116
|
+
return results;
|
|
117
|
+
}
|
|
118
|
+
async function loadPrincipalHistoryEntryJson(db, scopeId, leafHashes) {
|
|
119
|
+
const entries = new Map();
|
|
120
|
+
for (const batch of hashBatches([...new Set(leafHashes)])) {
|
|
121
|
+
const rows = await db
|
|
122
|
+
.select({
|
|
123
|
+
leafHash: principalHistoryEntries.leafHash,
|
|
124
|
+
entryJson: principalHistoryEntries.entryJson,
|
|
125
|
+
})
|
|
126
|
+
.from(principalHistoryEntries)
|
|
127
|
+
.where(and(eq(principalHistoryEntries.scopeId, scopeId), inArray(principalHistoryEntries.leafHash, batch)));
|
|
128
|
+
for (const row of rows)
|
|
129
|
+
entries.set(row.leafHash, row.entryJson);
|
|
130
|
+
}
|
|
131
|
+
return entries;
|
|
132
|
+
}
|
|
133
|
+
function parsePrincipalHistoryEntry(entryJson) {
|
|
73
134
|
let entry;
|
|
74
135
|
try {
|
|
75
|
-
entry =
|
|
136
|
+
entry = entryJson === undefined ? null : JSON.parse(entryJson);
|
|
76
137
|
}
|
|
77
138
|
catch {
|
|
78
139
|
entry = null;
|
|
79
140
|
}
|
|
80
141
|
if (!isPrincipalPolicyStateChainEntryResponse(entry))
|
|
81
142
|
throw new KeyingVerificationError("missing_dependency", "Principal history entry is missing or corrupt");
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
143
|
+
return entry;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Returns untrusted material, in `versions` order. The caller must verify it
|
|
147
|
+
* against the issued root. Index nodes are read one level at a time for every
|
|
148
|
+
* version and entries in one set read; the first version that fails, in
|
|
149
|
+
* order, throws the error loading it alone would.
|
|
150
|
+
*/
|
|
151
|
+
export async function loadPrincipalHistoryReferences(input) {
|
|
152
|
+
await ensureSqlTables(input.execSql, principalHistoryEvidenceTables);
|
|
153
|
+
const { db } = getClientSQLitePersistenceRuntime(input.execSql);
|
|
154
|
+
const resolved = await resolvePrincipalHistoryProofs(db, input);
|
|
155
|
+
const entries = await loadPrincipalHistoryEntryJson(db, input.scopeId, resolved.flatMap((result) => "value" in result ? [result.value.leafHash] : []));
|
|
156
|
+
return resolved.map((result) => {
|
|
157
|
+
if (!("value" in result))
|
|
158
|
+
throw result.error;
|
|
159
|
+
const entry = parsePrincipalHistoryEntry(entries.get(result.value.leafHash));
|
|
160
|
+
return {
|
|
161
|
+
reference: {
|
|
162
|
+
principalType: entry.state.principalType,
|
|
163
|
+
principalId: entry.state.principalId,
|
|
164
|
+
version: entry.state.version,
|
|
165
|
+
keyEpoch: entry.state.keyEpoch,
|
|
166
|
+
stateHash: entry.state.stateHash,
|
|
167
|
+
keyFingerprint: entry.state.keyFingerprint,
|
|
168
|
+
},
|
|
169
|
+
entry,
|
|
170
|
+
proof: result.value.proof,
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** Returns untrusted material. The caller must verify it against the issued root. */
|
|
175
|
+
export async function loadPrincipalHistoryReference(input) {
|
|
176
|
+
const [loaded] = await loadPrincipalHistoryReferences({
|
|
177
|
+
...input,
|
|
178
|
+
versions: [input.version],
|
|
179
|
+
});
|
|
180
|
+
if (!loaded)
|
|
181
|
+
throw new Error("Principal history reference was not loaded");
|
|
182
|
+
return input.expectedReference
|
|
183
|
+
? { ...loaded, reference: input.expectedReference }
|
|
184
|
+
: loaded;
|
|
91
185
|
}
|
|
92
186
|
//# sourceMappingURL=principalHistoryEvidencePersistence.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"principalHistoryEvidencePersistence.js","sourceRoot":"","sources":["principalHistoryEvidencePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n compareCanonicalStrings,\n KeyingVerificationError,\n PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT,\n type PrincipalHistoryIndexNode,\n type PrincipalPolicyHistoryReferenceProof,\n principalHistoryIndexLeaf,\n type ReferencedPrincipalHead,\n resolvePrincipalHistoryIndexProof,\n type VerifiedPrincipalPolicyHistory,\n} from \"@tearleads/crypto\";\nimport {\n isPrincipalPolicyStateChainEntryResponse,\n type PrincipalPolicyStateChainEntryResponse,\n} from \"@tearleads/validators/response\";\nimport { and, eq } from \"drizzle-orm\";\nimport {\n principalHistoryEntries,\n principalHistoryEvidenceTables,\n principalHistoryNodes,\n} from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport { sqliteInsertBatchSize } from \"../../sqlite/sqliteBindBudget\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { excludedValue } from \"../../sqlite/sqliteUpsert\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { writePrincipalHistoryNodes } from \"./principalHistoryNodeRetention\";\n\nconst ENTRY_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryEntries);\n\nexport interface PrincipalHistoryEvidencePage {\n readonly indexRootHash: string;\n readonly scopeId: string;\n readonly organizationId: string;\n readonly entries: readonly {\n readonly leafHash: string;\n readonly entryJson: string;\n }[];\n readonly nodes: readonly PrincipalHistoryIndexNode[];\n}\n\n/** Prepare only a page accepted by the signature/authorization verifier. */\nexport async function preparePrincipalHistoryEvidencePage(input: {\n readonly indexRootHash: string;\n readonly scopeId: string;\n readonly organizationId: string;\n readonly entries: readonly PrincipalPolicyStateChainEntryResponse[];\n readonly nodes: readonly PrincipalHistoryIndexNode[];\n}): Promise<PrincipalHistoryEvidencePage> {\n if (\n input.entries.length > PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT ||\n input.nodes.length > 2 * PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT\n )\n throw new KeyingVerificationError(\n \"invalid_shape\",\n \"Principal evidence page exceeds its batch budget\",\n );\n const owned = structuredClone(input);\n return {\n ...owned,\n entries: await Promise.all(\n owned.entries.map(async (entry) => ({\n leafHash: await principalHistoryIndexLeaf(entry.state),\n entryJson: JSON.stringify(entry),\n })),\n ),\n };\n}\n\n/** Publish in the same guarded transaction as the accepted page's progress. */\nexport async function writePrincipalHistoryEvidencePage(\n tx: ClientSQLiteTransactionScope,\n page: PrincipalHistoryEvidencePage,\n): Promise<void> {\n const scope = { scopeId: page.scopeId, organizationId: page.organizationId };\n // A repeated leaf keeps its last entry, as sequential upserts would.\n const entries = new Map(\n page.entries.map((entry) => [\n entry.leafHash,\n { ...scope, leafHash: entry.leafHash, entryJson: entry.entryJson },\n ]),\n );\n const rows = [...entries.values()].sort((a, b) =>\n compareCanonicalStrings(a.leafHash, b.leafHash),\n );\n for (let index = 0; index < rows.length; index += ENTRY_WRITE_BATCH_SIZE)\n await tx\n .insert(principalHistoryEntries)\n .values(rows.slice(index, index + ENTRY_WRITE_BATCH_SIZE))\n .onConflictDoUpdate({\n target: [\n principalHistoryEntries.scopeId,\n principalHistoryEntries.leafHash,\n ],\n set: {\n organizationId: excludedValue(principalHistoryEntries.organizationId),\n entryJson: excludedValue(principalHistoryEntries.entryJson),\n },\n })\n .run();\n await writePrincipalHistoryNodes(\n tx,\n scope,\n [...page.nodes].sort((a, b) => compareCanonicalStrings(a.hash, b.hash)),\n );\n}\n\n/** Returns untrusted material. The caller must verify it against the issued root. */\nexport async function loadPrincipalHistoryReference(input: {\n readonly execSql: ExecSql;\n readonly scopeId: string;\n readonly history: VerifiedPrincipalPolicyHistory;\n readonly version: number;\n readonly expectedReference?: ReferencedPrincipalHead | undefined;\n}): Promise<PrincipalPolicyHistoryReferenceProof> {\n await ensureSqlTables(input.execSql, principalHistoryEvidenceTables);\n const { db } = getClientSQLitePersistenceRuntime(input.execSql);\n const resolved = await resolvePrincipalHistoryIndexProof({\n rootHash: input.history.indexRootHash,\n treeSize: input.history.currentEntry.state.version,\n version: input.version,\n readNode: async (hash) => {\n const [row] = await db\n .select()\n .from(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, input.scopeId),\n eq(principalHistoryNodes.hash, hash),\n ),\n )\n .limit(1);\n return row ?? null;\n },\n });\n const [row] = await db\n .select()\n .from(principalHistoryEntries)\n .where(\n and(\n eq(principalHistoryEntries.scopeId, input.scopeId),\n eq(principalHistoryEntries.leafHash, resolved.leafHash),\n ),\n )\n .limit(1);\n let entry: unknown;\n try {\n entry = row ? JSON.parse(row.entryJson) : null;\n } catch {\n entry = null;\n }\n if (!isPrincipalPolicyStateChainEntryResponse(entry))\n throw new KeyingVerificationError(\n \"missing_dependency\",\n \"Principal history entry is missing or corrupt\",\n );\n const reference = input.expectedReference ?? {\n principalType: entry.state.principalType,\n principalId: entry.state.principalId,\n version: entry.state.version,\n keyEpoch: entry.state.keyEpoch,\n stateHash: entry.state.stateHash,\n keyFingerprint: entry.state.keyFingerprint,\n };\n return { reference, entry, proof: resolved.proof };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"principalHistoryEvidencePersistence.js","sourceRoot":"","sources":["principalHistoryEvidencePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n compareCanonicalStrings,\n KeyingVerificationError,\n PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT,\n type PrincipalHistoryIndexNode,\n type PrincipalPolicyHistoryReferenceProof,\n principalHistoryIndexLeaf,\n type ReferencedPrincipalHead,\n resolvePrincipalHistoryIndexProof,\n type VerifiedPrincipalPolicyHistory,\n} from \"@tearleads/crypto\";\nimport {\n isPrincipalPolicyStateChainEntryResponse,\n type PrincipalPolicyStateChainEntryResponse,\n} from \"@tearleads/validators/response\";\nimport { and, eq, inArray } from \"drizzle-orm\";\nimport {\n principalHistoryEntries,\n principalHistoryEvidenceTables,\n principalHistoryNodes,\n} from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport {\n sqliteInsertBatchSize,\n sqliteRowsPerStatement,\n} from \"../../sqlite/sqliteBindBudget\";\nimport {\n type ClientSQLiteDatabase,\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { excludedValue } from \"../../sqlite/sqliteUpsert\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { writePrincipalHistoryNodes } from \"./principalHistoryNodeRetention\";\nimport type { PrincipalHistoryRootEdges } from \"./principalHistoryRootOwnership\";\n\nconst ENTRY_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryEntries);\n\nexport interface PrincipalHistoryEvidencePage {\n readonly indexRootHash: string;\n readonly scopeId: string;\n readonly organizationId: string;\n readonly entries: readonly {\n readonly leafHash: string;\n readonly entryJson: string;\n }[];\n readonly nodes: readonly PrincipalHistoryIndexNode[];\n}\n\n/** Prepare only a page accepted by the signature/authorization verifier. */\nexport async function preparePrincipalHistoryEvidencePage(input: {\n readonly indexRootHash: string;\n readonly scopeId: string;\n readonly organizationId: string;\n readonly entries: readonly PrincipalPolicyStateChainEntryResponse[];\n readonly nodes: readonly PrincipalHistoryIndexNode[];\n}): Promise<PrincipalHistoryEvidencePage> {\n if (\n input.entries.length > PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT ||\n input.nodes.length > 2 * PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT\n )\n throw new KeyingVerificationError(\n \"invalid_shape\",\n \"Principal evidence page exceeds its batch budget\",\n );\n const owned = structuredClone(input);\n return {\n ...owned,\n entries: await Promise.all(\n owned.entries.map(async (entry) => ({\n leafHash: await principalHistoryIndexLeaf(entry.state),\n entryJson: JSON.stringify(entry),\n })),\n ),\n };\n}\n\n/** Publish in the same guarded transaction as the accepted page's progress. */\nexport async function writePrincipalHistoryEvidencePage(\n tx: ClientSQLiteTransactionScope,\n page: PrincipalHistoryEvidencePage,\n rootEdges?: PrincipalHistoryRootEdges | undefined,\n): Promise<void> {\n const scope = { scopeId: page.scopeId, organizationId: page.organizationId };\n // A repeated leaf keeps its last entry, as sequential upserts would.\n const entries = new Map(\n page.entries.map((entry) => [\n entry.leafHash,\n { ...scope, leafHash: entry.leafHash, entryJson: entry.entryJson },\n ]),\n );\n const rows = [...entries.values()].sort((a, b) =>\n compareCanonicalStrings(a.leafHash, b.leafHash),\n );\n for (let index = 0; index < rows.length; index += ENTRY_WRITE_BATCH_SIZE)\n await tx\n .insert(principalHistoryEntries)\n .values(rows.slice(index, index + ENTRY_WRITE_BATCH_SIZE))\n .onConflictDoUpdate({\n target: [\n principalHistoryEntries.scopeId,\n principalHistoryEntries.leafHash,\n ],\n set: {\n organizationId: excludedValue(principalHistoryEntries.organizationId),\n entryJson: excludedValue(principalHistoryEntries.entryJson),\n },\n })\n .run();\n await writePrincipalHistoryNodes(\n tx,\n scope,\n [...page.nodes].sort((a, b) => compareCanonicalStrings(a.hash, b.hash)),\n rootEdges,\n );\n}\n\ntype PrincipalHistoryNodeRow = typeof principalHistoryNodes.$inferSelect;\ntype ResolvedPrincipalHistoryProof = Awaited<\n ReturnType<typeof resolvePrincipalHistoryIndexProof>\n>;\n\n// The scope id binds once per lookup.\nconst HASH_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);\n\n/** Thrown by the preloaded node reader to ask the next level's set read for a hash. */\nclass PrincipalHistoryNodeNotLoaded {\n constructor(readonly hash: string) {}\n}\n\nfunction hashBatches(hashes: readonly string[]): string[][] {\n const result: string[][] = [];\n for (let index = 0; index < hashes.length; index += HASH_LOOKUP_BATCH_SIZE)\n result.push(hashes.slice(index, index + HASH_LOOKUP_BATCH_SIZE));\n return result;\n}\n\n/**\n * Resolves every version's index path level by level: each pass walks every\n * unfinished version over the nodes read so far, and one set read per level\n * supplies the nodes the walks stopped at. A node absent after its read stays\n * missing, so the unchanged verifier fails that version as it would serially.\n */\nasync function resolvePrincipalHistoryProofs(\n db: ClientSQLiteDatabase,\n input: {\n readonly scopeId: string;\n readonly history: VerifiedPrincipalPolicyHistory;\n readonly versions: readonly number[];\n },\n): Promise<({ value: ResolvedPrincipalHistoryProof } | { error: unknown })[]> {\n const nodes = new Map<string, PrincipalHistoryNodeRow | null>();\n const results: (\n | { value: ResolvedPrincipalHistoryProof }\n | { error: unknown }\n )[] = [];\n let pending = input.versions.map((_, index) => index);\n while (pending.length > 0) {\n const needed = new Set<string>();\n const unfinished: number[] = [];\n for (const index of pending) {\n try {\n results[index] = {\n value: await resolvePrincipalHistoryIndexProof({\n rootHash: input.history.indexRootHash,\n treeSize: input.history.currentEntry.state.version,\n version: input.versions[index] ?? 0,\n readNode: async (hash) => {\n const node = nodes.get(hash);\n if (node === undefined)\n throw new PrincipalHistoryNodeNotLoaded(hash);\n return node;\n },\n }),\n };\n } catch (error) {\n if (!(error instanceof PrincipalHistoryNodeNotLoaded)) {\n results[index] = { error };\n continue;\n }\n needed.add(error.hash);\n unfinished.push(index);\n }\n }\n for (const batch of hashBatches([...needed])) {\n for (const hash of batch) nodes.set(hash, null);\n const rows = await db\n .select()\n .from(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, input.scopeId),\n inArray(principalHistoryNodes.hash, batch),\n ),\n );\n for (const row of rows) nodes.set(row.hash, row);\n }\n pending = unfinished;\n }\n return results;\n}\n\nasync function loadPrincipalHistoryEntryJson(\n db: ClientSQLiteDatabase,\n scopeId: string,\n leafHashes: readonly string[],\n): Promise<Map<string, string>> {\n const entries = new Map<string, string>();\n for (const batch of hashBatches([...new Set(leafHashes)])) {\n const rows = await db\n .select({\n leafHash: principalHistoryEntries.leafHash,\n entryJson: principalHistoryEntries.entryJson,\n })\n .from(principalHistoryEntries)\n .where(\n and(\n eq(principalHistoryEntries.scopeId, scopeId),\n inArray(principalHistoryEntries.leafHash, batch),\n ),\n );\n for (const row of rows) entries.set(row.leafHash, row.entryJson);\n }\n return entries;\n}\n\nfunction parsePrincipalHistoryEntry(\n entryJson: string | undefined,\n): PrincipalPolicyStateChainEntryResponse {\n let entry: unknown;\n try {\n entry = entryJson === undefined ? null : JSON.parse(entryJson);\n } catch {\n entry = null;\n }\n if (!isPrincipalPolicyStateChainEntryResponse(entry))\n throw new KeyingVerificationError(\n \"missing_dependency\",\n \"Principal history entry is missing or corrupt\",\n );\n return entry;\n}\n\n/**\n * Returns untrusted material, in `versions` order. The caller must verify it\n * against the issued root. Index nodes are read one level at a time for every\n * version and entries in one set read; the first version that fails, in\n * order, throws the error loading it alone would.\n */\nexport async function loadPrincipalHistoryReferences(input: {\n readonly execSql: ExecSql;\n readonly scopeId: string;\n readonly history: VerifiedPrincipalPolicyHistory;\n readonly versions: readonly number[];\n}): Promise<PrincipalPolicyHistoryReferenceProof[]> {\n await ensureSqlTables(input.execSql, principalHistoryEvidenceTables);\n const { db } = getClientSQLitePersistenceRuntime(input.execSql);\n const resolved = await resolvePrincipalHistoryProofs(db, input);\n const entries = await loadPrincipalHistoryEntryJson(\n db,\n input.scopeId,\n resolved.flatMap((result) =>\n \"value\" in result ? [result.value.leafHash] : [],\n ),\n );\n return resolved.map((result) => {\n if (!(\"value\" in result)) throw result.error;\n const entry = parsePrincipalHistoryEntry(\n entries.get(result.value.leafHash),\n );\n return {\n reference: {\n principalType: entry.state.principalType,\n principalId: entry.state.principalId,\n version: entry.state.version,\n keyEpoch: entry.state.keyEpoch,\n stateHash: entry.state.stateHash,\n keyFingerprint: entry.state.keyFingerprint,\n },\n entry,\n proof: result.value.proof,\n };\n });\n}\n\n/** Returns untrusted material. The caller must verify it against the issued root. */\nexport async function loadPrincipalHistoryReference(input: {\n readonly execSql: ExecSql;\n readonly scopeId: string;\n readonly history: VerifiedPrincipalPolicyHistory;\n readonly version: number;\n readonly expectedReference?: ReferencedPrincipalHead | undefined;\n}): Promise<PrincipalPolicyHistoryReferenceProof> {\n const [loaded] = await loadPrincipalHistoryReferences({\n ...input,\n versions: [input.version],\n });\n if (!loaded) throw new Error(\"Principal history reference was not loaded\");\n return input.expectedReference\n ? { ...loaded, reference: input.expectedReference }\n : loaded;\n}\n"]}
|