@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"principalGrantRetirementPersistence.js","sourceRoot":"","sources":["principalGrantRetirementPersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,uBAAuB,GAGxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"principalGrantRetirementPersistence.js","sourceRoot":"","sources":["principalGrantRetirementPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AccessManifestCheckpoint,\n KeyingVerificationError,\n type VerifiedPrincipalPolicy,\n type VerifiedPrincipalPolicyCurrent,\n} from \"@tearleads/crypto\";\nimport { and, eq, inArray } from \"drizzle-orm\";\nimport { principalGrantRetirements } from \"../../sqlite/schema/principalGrantRetirementSchema\";\nimport { sqliteRowsPerStatement } from \"../../sqlite/sqliteBindBudget\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\n\nexport interface AcknowledgedPrincipalGrantRetirements {\n readonly containerIds: readonly string[];\n readonly organizationId: string;\n readonly policy: VerifiedPrincipalPolicy | VerifiedPrincipalPolicyCurrent;\n}\n\nexport async function storePrincipalGrantRetirements(\n tx: ClientSQLiteTransactionScope,\n retirement: AcknowledgedPrincipalGrantRetirements,\n policies: readonly (\n | VerifiedPrincipalPolicy\n | VerifiedPrincipalPolicyCurrent\n )[],\n organizationId: string | undefined,\n): Promise<void> {\n if (\n !policies.some(\n (policy) =>\n policy.principalId === retirement.policy.principalId &&\n policy.principalType === retirement.policy.principalType &&\n policy.stateHash === retirement.policy.stateHash,\n )\n ) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"Retirement must accompany its acknowledged policy\",\n );\n }\n const history =\n \"retainedHistory\" in retirement.policy\n ? retirement.policy.retainedHistory\n : retirement.policy.history;\n const previous = history?.find(\n (entry) => entry.state.stateHash === retirement.policy.state.prevStateHash,\n );\n const granted = new Set(\n [...retirement.policy.grants, ...(previous?.grants ?? [])].map(\n (grant) => grant.containerId,\n ),\n );\n if (\n retirement.organizationId !== organizationId ||\n retirement.containerIds.some((id) => !granted.has(id))\n ) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"Retirement must match the acknowledged organization and signed grants\",\n );\n }\n for (const containerId of new Set(retirement.containerIds)) {\n await tx\n .insert(principalGrantRetirements)\n .values({\n organizationId: retirement.organizationId,\n containerId,\n principalId: retirement.policy.principalId,\n policyStateHash: retirement.policy.stateHash,\n })\n .onConflictDoNothing()\n .run();\n }\n}\n\nexport async function assertContainerNotRetired(\n tx: ClientSQLiteTransactionScope,\n checkpoint: AccessManifestCheckpoint,\n): Promise<void> {\n if (checkpoint.objectKind !== \"container\") return;\n const [retired] = await tx\n .select({ containerId: principalGrantRetirements.containerId })\n .from(principalGrantRetirements)\n .where(\n and(\n eq(principalGrantRetirements.organizationId, checkpoint.organizationId),\n eq(principalGrantRetirements.containerId, checkpoint.objectId),\n ),\n )\n .limit(1);\n if (retired)\n throw new KeyingVerificationError(\n \"equivocation\",\n `Container ${checkpoint.objectId} was retired during principal rotation`,\n );\n}\n\n// A lookup binds its organization once.\nconst RETIREMENT_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);\n\nfunction retiredContainerKey(organizationId: string, containerId: string) {\n return JSON.stringify([organizationId, containerId]);\n}\n\n/**\n * The set form of `assertContainerNotRetired` for a batch of checkpoints: one\n * read per organization. The returned check throws the same error for each\n * checkpoint, so a caller replaying its batch in order fails at the same one.\n */\nexport async function loadContainerRetirementCheck(\n tx: ClientSQLiteTransactionScope,\n checkpoints: readonly AccessManifestCheckpoint[],\n): Promise<(checkpoint: AccessManifestCheckpoint) => void> {\n const idsByOrganization = new Map<string, Set<string>>();\n for (const checkpoint of checkpoints)\n if (checkpoint.objectKind === \"container\")\n idsByOrganization.set(\n checkpoint.organizationId,\n (idsByOrganization.get(checkpoint.organizationId) ?? new Set()).add(\n checkpoint.objectId,\n ),\n );\n const retired = new Set<string>();\n for (const [organizationId, ids] of idsByOrganization) {\n const all = [...ids];\n for (\n let index = 0;\n index < all.length;\n index += RETIREMENT_LOOKUP_BATCH_SIZE\n ) {\n const rows = await tx\n .select({ containerId: principalGrantRetirements.containerId })\n .from(principalGrantRetirements)\n .where(\n and(\n eq(principalGrantRetirements.organizationId, organizationId),\n inArray(\n principalGrantRetirements.containerId,\n all.slice(index, index + RETIREMENT_LOOKUP_BATCH_SIZE),\n ),\n ),\n );\n for (const row of rows)\n retired.add(retiredContainerKey(organizationId, row.containerId));\n }\n }\n return (checkpoint) => {\n if (\n checkpoint.objectKind === \"container\" &&\n retired.has(\n retiredContainerKey(checkpoint.organizationId, checkpoint.objectId),\n )\n )\n throw new KeyingVerificationError(\n \"equivocation\",\n `Container ${checkpoint.objectId} was retired during principal rotation`,\n );\n };\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
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 { principalPolicyOrganizations } from "../../sqlite/schema/index.js";
|
|
4
|
+
import { sqliteInsertBatchSize, sqliteRowsPerStatement, } from "../../sqlite/sqliteBindBudget.js";
|
|
4
5
|
function resolveOrganizationId(input) {
|
|
5
6
|
if (input.principalType === "organization") {
|
|
6
7
|
if (input.organizationId !== undefined &&
|
|
@@ -32,6 +33,66 @@ export async function recordPrincipalPolicyOrganizationInTransaction(tx, input)
|
|
|
32
33
|
.onConflictDoNothing()
|
|
33
34
|
.run();
|
|
34
35
|
}
|
|
36
|
+
// A lookup binds its principal type once.
|
|
37
|
+
const OWNER_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);
|
|
38
|
+
const OWNER_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalPolicyOrganizations);
|
|
39
|
+
function slices(values, size) {
|
|
40
|
+
const result = [];
|
|
41
|
+
for (let index = 0; index < values.length; index += size)
|
|
42
|
+
result.push(values.slice(index, index + size));
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
async function loadPrincipalPolicyOrganizations(tx, inputs) {
|
|
46
|
+
const owners = new Map();
|
|
47
|
+
const idsByType = new Map();
|
|
48
|
+
for (const input of inputs)
|
|
49
|
+
idsByType.set(input.principalType, (idsByType.get(input.principalType) ?? new Set()).add(input.principalId));
|
|
50
|
+
for (const [principalType, ids] of idsByType)
|
|
51
|
+
for (const batch of slices([...ids], OWNER_LOOKUP_BATCH_SIZE)) {
|
|
52
|
+
const rows = await tx
|
|
53
|
+
.select({
|
|
54
|
+
principalId: principalPolicyOrganizations.principalId,
|
|
55
|
+
organizationId: principalPolicyOrganizations.organizationId,
|
|
56
|
+
})
|
|
57
|
+
.from(principalPolicyOrganizations)
|
|
58
|
+
.where(and(eq(principalPolicyOrganizations.principalType, principalType), inArray(principalPolicyOrganizations.principalId, batch)));
|
|
59
|
+
for (const row of rows)
|
|
60
|
+
owners.set(JSON.stringify([principalType, row.principalId]), row.organizationId);
|
|
61
|
+
}
|
|
62
|
+
return owners;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The set form of `recordPrincipalPolicyOrganizationInTransaction`: one read
|
|
66
|
+
* per principal type, the same checks replayed in order (so the first failing
|
|
67
|
+
* input throws the same error), then one multi-row insert of the new owners.
|
|
68
|
+
*/
|
|
69
|
+
export async function recordPrincipalPolicyOrganizationsInTransaction(tx, inputs) {
|
|
70
|
+
if (inputs.length === 0)
|
|
71
|
+
return;
|
|
72
|
+
const owners = await loadPrincipalPolicyOrganizations(tx, inputs);
|
|
73
|
+
const inserted = [];
|
|
74
|
+
for (const input of inputs) {
|
|
75
|
+
const organizationId = resolveOrganizationId(input);
|
|
76
|
+
const key = JSON.stringify([input.principalType, input.principalId]);
|
|
77
|
+
const stored = owners.get(key);
|
|
78
|
+
if (stored !== undefined && stored !== organizationId)
|
|
79
|
+
throw new KeyingVerificationError("object_mismatch", "principal policy cache is already owned by another organization");
|
|
80
|
+
if (stored !== undefined)
|
|
81
|
+
continue;
|
|
82
|
+
owners.set(key, organizationId);
|
|
83
|
+
inserted.push({
|
|
84
|
+
principalType: input.principalType,
|
|
85
|
+
principalId: input.principalId,
|
|
86
|
+
organizationId,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
for (const batch of slices(inserted, OWNER_WRITE_BATCH_SIZE))
|
|
90
|
+
await tx
|
|
91
|
+
.insert(principalPolicyOrganizations)
|
|
92
|
+
.values(batch)
|
|
93
|
+
.onConflictDoNothing()
|
|
94
|
+
.run();
|
|
95
|
+
}
|
|
35
96
|
export function recordPrincipalPolicyBundleOrganizationInTransaction(tx, bundle, organizationId) {
|
|
36
97
|
return recordPrincipalPolicyOrganizationInTransaction(tx, {
|
|
37
98
|
organizationId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"principalPolicyOrganizationPersistence.js","sourceRoot":"","sources":["principalPolicyOrganizationPersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"principalPolicyOrganizationPersistence.js","sourceRoot":"","sources":["principalPolicyOrganizationPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport type { PrincipalStateResponse } from \"@tearleads/validators/response\";\nimport { and, eq, inArray } from \"drizzle-orm\";\nimport { principalPolicyOrganizations } from \"../../sqlite/schema\";\nimport {\n sqliteInsertBatchSize,\n sqliteRowsPerStatement,\n} from \"../../sqlite/sqliteBindBudget\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\n\ninterface PrincipalPolicyOrganizationInput {\n readonly organizationId?: string | undefined;\n readonly principalId: string;\n readonly principalType: PrincipalStateResponse[\"principalType\"];\n}\n\nfunction resolveOrganizationId(\n input: PrincipalPolicyOrganizationInput,\n): string {\n if (input.principalType === \"organization\") {\n if (\n input.organizationId !== undefined &&\n input.organizationId !== input.principalId\n ) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"organization policy cache ownership targets another organization\",\n );\n }\n return input.principalId;\n }\n if (!input.organizationId) {\n throw new Error(\"Group policy cache writes require an organization ID\");\n }\n return input.organizationId;\n}\n\n/** Pins cache ownership in the same transaction as the cached policy state. */\nexport async function recordPrincipalPolicyOrganizationInTransaction(\n tx: ClientSQLiteTransactionScope,\n input: PrincipalPolicyOrganizationInput,\n): Promise<void> {\n const organizationId = resolveOrganizationId(input);\n const wherePrincipal = and(\n eq(principalPolicyOrganizations.principalType, input.principalType),\n eq(principalPolicyOrganizations.principalId, input.principalId),\n );\n const [stored] = await tx\n .select({ organizationId: principalPolicyOrganizations.organizationId })\n .from(principalPolicyOrganizations)\n .where(wherePrincipal)\n .limit(1);\n if (stored && stored.organizationId !== organizationId) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"principal policy cache is already owned by another organization\",\n );\n }\n await tx\n .insert(principalPolicyOrganizations)\n .values({ ...input, organizationId })\n .onConflictDoNothing()\n .run();\n}\n\n// A lookup binds its principal type once.\nconst OWNER_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);\nconst OWNER_WRITE_BATCH_SIZE = sqliteInsertBatchSize(\n principalPolicyOrganizations,\n);\n\nfunction slices<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\nasync function loadPrincipalPolicyOrganizations(\n tx: ClientSQLiteTransactionScope,\n inputs: readonly PrincipalPolicyOrganizationInput[],\n): Promise<Map<string, string>> {\n const owners = new Map<string, string>();\n const idsByType = new Map<\n PrincipalPolicyOrganizationInput[\"principalType\"],\n Set<string>\n >();\n for (const input of inputs)\n idsByType.set(\n input.principalType,\n (idsByType.get(input.principalType) ?? new Set()).add(input.principalId),\n );\n for (const [principalType, ids] of idsByType)\n for (const batch of slices([...ids], OWNER_LOOKUP_BATCH_SIZE)) {\n const rows = await tx\n .select({\n principalId: principalPolicyOrganizations.principalId,\n organizationId: principalPolicyOrganizations.organizationId,\n })\n .from(principalPolicyOrganizations)\n .where(\n and(\n eq(principalPolicyOrganizations.principalType, principalType),\n inArray(principalPolicyOrganizations.principalId, batch),\n ),\n );\n for (const row of rows)\n owners.set(\n JSON.stringify([principalType, row.principalId]),\n row.organizationId,\n );\n }\n return owners;\n}\n\n/**\n * The set form of `recordPrincipalPolicyOrganizationInTransaction`: one read\n * per principal type, the same checks replayed in order (so the first failing\n * input throws the same error), then one multi-row insert of the new owners.\n */\nexport async function recordPrincipalPolicyOrganizationsInTransaction(\n tx: ClientSQLiteTransactionScope,\n inputs: readonly PrincipalPolicyOrganizationInput[],\n): Promise<void> {\n if (inputs.length === 0) return;\n const owners = await loadPrincipalPolicyOrganizations(tx, inputs);\n const inserted: (PrincipalPolicyOrganizationInput & {\n readonly organizationId: string;\n })[] = [];\n for (const input of inputs) {\n const organizationId = resolveOrganizationId(input);\n const key = JSON.stringify([input.principalType, input.principalId]);\n const stored = owners.get(key);\n if (stored !== undefined && stored !== organizationId)\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"principal policy cache is already owned by another organization\",\n );\n if (stored !== undefined) continue;\n owners.set(key, organizationId);\n inserted.push({\n principalType: input.principalType,\n principalId: input.principalId,\n organizationId,\n });\n }\n for (const batch of slices(inserted, OWNER_WRITE_BATCH_SIZE))\n await tx\n .insert(principalPolicyOrganizations)\n .values(batch)\n .onConflictDoNothing()\n .run();\n}\n\nexport function recordPrincipalPolicyBundleOrganizationInTransaction(\n tx: ClientSQLiteTransactionScope,\n bundle: { readonly currentState: PrincipalStateResponse },\n organizationId?: string | undefined,\n): Promise<void> {\n return recordPrincipalPolicyOrganizationInTransaction(tx, {\n organizationId,\n principalId: bundle.currentState.principalId,\n principalType: bundle.currentState.principalType,\n });\n}\n"]}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { and, eq } from "drizzle-orm";
|
|
1
|
+
import { and, eq, or } from "drizzle-orm";
|
|
2
2
|
import { trustedUserIdentityPins, trustedUserIdentityPinTables, } from "../sqlite/schema/index.js";
|
|
3
3
|
import { getClientSQLitePersistenceRuntime, } from "../sqlite/sqlitePersistenceRuntime.js";
|
|
4
4
|
import { ensureSqlTables } from "../sqlite/sqlSchema.js";
|
|
5
5
|
import { TRUSTED_USER_IDENTITY_ENCAPSULATION_SUITE, TRUSTED_USER_IDENTITY_FORMAT_VERSION, TRUSTED_USER_IDENTITY_SIGNING_SUITE, } from "../trustedUserIdentity/types.js";
|
|
6
6
|
import { runTrustedUserIdentityPinTransaction } from "./trustedUserIdentityPinTransaction.js";
|
|
7
|
+
const provenanceRank = { observed: 0, link: 1, self: 2 };
|
|
8
|
+
function isTrustedUserIdentityPinProvenance(value) {
|
|
9
|
+
return value === "self" || value === "link" || value === "observed";
|
|
10
|
+
}
|
|
7
11
|
const comparedPinFields = [
|
|
8
12
|
"userId",
|
|
9
13
|
"formatVersion",
|
|
@@ -117,6 +121,9 @@ function validationFailure(value) {
|
|
|
117
121
|
!Number.isFinite(Date.parse(firstSeenAt))) {
|
|
118
122
|
return "firstSeenAt must be a valid timestamp";
|
|
119
123
|
}
|
|
124
|
+
if (!isTrustedUserIdentityPinProvenance(Reflect.get(value, "provenance"))) {
|
|
125
|
+
return "provenance must be self, link, or observed";
|
|
126
|
+
}
|
|
120
127
|
return null;
|
|
121
128
|
}
|
|
122
129
|
function validatedStringField(value, field) {
|
|
@@ -133,6 +140,13 @@ function validatedFormatVersion(value) {
|
|
|
133
140
|
}
|
|
134
141
|
return formatVersion;
|
|
135
142
|
}
|
|
143
|
+
function validatedProvenance(value) {
|
|
144
|
+
const provenance = Reflect.get(value, "provenance");
|
|
145
|
+
if (!isTrustedUserIdentityPinProvenance(provenance)) {
|
|
146
|
+
throw new TypeError("Validated pin provenance is not a provenance");
|
|
147
|
+
}
|
|
148
|
+
return provenance;
|
|
149
|
+
}
|
|
136
150
|
function assertCandidatePin(pin) {
|
|
137
151
|
const failure = validationFailure(pin);
|
|
138
152
|
if (failure) {
|
|
@@ -158,6 +172,7 @@ function parseStoredPin(value, scope) {
|
|
|
158
172
|
encapsulationPublicKey: validatedStringField(value, "encapsulationPublicKey"),
|
|
159
173
|
encapsulationKeyFingerprint: validatedStringField(value, "encapsulationKeyFingerprint"),
|
|
160
174
|
firstSeenAt: validatedStringField(value, "firstSeenAt"),
|
|
175
|
+
provenance: validatedProvenance(value),
|
|
161
176
|
};
|
|
162
177
|
if (pin.identityTrustDomain !== scope.identityTrustDomain ||
|
|
163
178
|
pin.userId !== scope.userId) {
|
|
@@ -199,53 +214,87 @@ export async function loadTrustedUserIdForSigningKey(input) {
|
|
|
199
214
|
return rows[0]?.userId ?? null;
|
|
200
215
|
}
|
|
201
216
|
/**
|
|
202
|
-
*
|
|
217
|
+
* One read answers an exact repeat of a pin that alone holds its
|
|
218
|
+
* key and ranks at or above the candidate; anything else returns null for the
|
|
219
|
+
* write transaction. Pins are never deleted, keys never change, and
|
|
220
|
+
* provenance only rises, so a match is what the transaction would return.
|
|
221
|
+
*/
|
|
222
|
+
const pins = trustedUserIdentityPins;
|
|
223
|
+
async function matchingPin(db, pin) {
|
|
224
|
+
const [row, other] = await db
|
|
225
|
+
.select()
|
|
226
|
+
.from(pins)
|
|
227
|
+
.where(and(eq(pins.identityTrustDomain, pin.identityTrustDomain), or(eq(pins.userId, pin.userId), eq(pins.signingKeyFingerprint, pin.signingKeyFingerprint))))
|
|
228
|
+
.limit(2);
|
|
229
|
+
if (other || row?.userId !== pin.userId || validationFailure(row))
|
|
230
|
+
return null;
|
|
231
|
+
const stored = parseStoredPin(row, pin);
|
|
232
|
+
const rank = provenanceRank;
|
|
233
|
+
if (differingPinFields(stored, pin).length > 0)
|
|
234
|
+
return null;
|
|
235
|
+
return rank[pin.provenance] <= rank[stored.provenance] ? stored : null;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Atomically establishes or verifies a durable identity pin.
|
|
203
239
|
*
|
|
204
240
|
* The immediate transaction serializes first contact across SDK instances that
|
|
205
241
|
* share the SQLite database. Conflict handling never replaces the existing
|
|
206
|
-
*
|
|
207
|
-
* this function returns.
|
|
242
|
+
* keys: the winning pin is re-read and every identity field is compared before
|
|
243
|
+
* this function returns. When the keys match and the candidate's provenance
|
|
244
|
+
* ranks higher (an identity link confirming an observed pin), only the
|
|
245
|
+
* provenance is raised; it is never lowered.
|
|
208
246
|
*/
|
|
209
247
|
export async function compareOrInsertTrustedUserIdentityPin(input) {
|
|
210
|
-
|
|
248
|
+
const { pin } = input;
|
|
249
|
+
assertCandidatePin(pin);
|
|
211
250
|
const runtime = getClientSQLitePersistenceRuntime(input.execSql);
|
|
212
251
|
return runTrustedUserIdentityPinTransaction(async () => {
|
|
213
252
|
await ensureSqlTables(input.execSql, trustedUserIdentityPinTables);
|
|
253
|
+
// Like the write path's BEGIN, this read's BEGIN retries while any adapter
|
|
254
|
+
// holds the connection's transaction, so it never sees uncommitted pins.
|
|
255
|
+
const pinned = await runtime.transaction((tx) => matchingPin(tx, pin));
|
|
256
|
+
if (pinned)
|
|
257
|
+
return pinned;
|
|
214
258
|
return runtime.transaction(async (tx) => {
|
|
215
259
|
const aliases = await tx
|
|
216
260
|
.select()
|
|
217
|
-
.from(
|
|
218
|
-
.where(and(eq(
|
|
261
|
+
.from(pins)
|
|
262
|
+
.where(and(eq(pins.identityTrustDomain, pin.identityTrustDomain), eq(pins.signingKeyFingerprint, pin.signingKeyFingerprint)))
|
|
219
263
|
.limit(1);
|
|
220
264
|
const alias = aliases[0];
|
|
221
|
-
if (alias && alias.userId !==
|
|
265
|
+
if (alias && alias.userId !== pin.userId) {
|
|
222
266
|
throw new TrustedUserIdentityPinMismatchError(parseStoredPin(alias, {
|
|
223
|
-
identityTrustDomain:
|
|
267
|
+
identityTrustDomain: pin.identityTrustDomain,
|
|
224
268
|
userId: alias.userId,
|
|
225
|
-
}),
|
|
269
|
+
}), pin, ["userId"]);
|
|
226
270
|
}
|
|
227
271
|
// Targets the primary key only: re-pinning the same user is a no-op,
|
|
228
272
|
// but a write that would bind this key to a second user must fail at
|
|
229
273
|
// the unique index rather than be silently skipped.
|
|
230
274
|
await tx
|
|
231
|
-
.insert(
|
|
232
|
-
.values(
|
|
275
|
+
.insert(pins)
|
|
276
|
+
.values(pin)
|
|
233
277
|
.onConflictDoNothing({
|
|
234
|
-
target: [
|
|
235
|
-
trustedUserIdentityPins.identityTrustDomain,
|
|
236
|
-
trustedUserIdentityPins.userId,
|
|
237
|
-
],
|
|
278
|
+
target: [pins.identityTrustDomain, pins.userId],
|
|
238
279
|
})
|
|
239
280
|
.run();
|
|
240
|
-
const stored = await selectStoredPin(tx,
|
|
281
|
+
const stored = await selectStoredPin(tx, pin);
|
|
241
282
|
if (!stored) {
|
|
242
|
-
throw new TrustedUserIdentityPinCorruptError(
|
|
283
|
+
throw new TrustedUserIdentityPinCorruptError(pin, "row is missing after compare-or-insert");
|
|
243
284
|
}
|
|
244
|
-
const differingFields = differingPinFields(stored,
|
|
285
|
+
const differingFields = differingPinFields(stored, pin);
|
|
245
286
|
if (differingFields.length > 0) {
|
|
246
|
-
throw new TrustedUserIdentityPinMismatchError(stored,
|
|
287
|
+
throw new TrustedUserIdentityPinMismatchError(stored, pin, differingFields);
|
|
288
|
+
}
|
|
289
|
+
if (provenanceRank[pin.provenance] <= provenanceRank[stored.provenance]) {
|
|
290
|
+
return stored;
|
|
247
291
|
}
|
|
248
|
-
|
|
292
|
+
await tx
|
|
293
|
+
.update(pins)
|
|
294
|
+
.set({ provenance: pin.provenance })
|
|
295
|
+
.where(and(eq(pins.identityTrustDomain, pin.identityTrustDomain), eq(pins.userId, pin.userId)))
|
|
296
|
+
.run();
|
|
297
|
+
return { ...stored, provenance: pin.provenance };
|
|
249
298
|
}, { behavior: "immediate" });
|
|
250
299
|
});
|
|
251
300
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trustedUserIdentityPinPersistence.js","sourceRoot":"","sources":["trustedUserIdentityPinPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport {\n trustedUserIdentityPins,\n trustedUserIdentityPinTables,\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 {\n TRUSTED_USER_IDENTITY_ENCAPSULATION_SUITE,\n TRUSTED_USER_IDENTITY_FORMAT_VERSION,\n TRUSTED_USER_IDENTITY_SIGNING_SUITE,\n} from \"../trustedUserIdentity/types\";\nimport { runTrustedUserIdentityPinTransaction } from \"./trustedUserIdentityPinTransaction\";\n\nexport interface TrustedUserIdentityPin {\n readonly identityTrustDomain: string;\n readonly userId: string;\n readonly formatVersion: number;\n readonly signingSuite: string;\n readonly signingPublicKey: string;\n readonly signingKeyFingerprint: string;\n readonly encapsulationSuite: string;\n readonly encapsulationPublicKey: string;\n readonly encapsulationKeyFingerprint: string;\n readonly firstSeenAt: string;\n}\n\ntype ComparedPinField =\n | \"userId\"\n | \"formatVersion\"\n | \"signingSuite\"\n | \"signingPublicKey\"\n | \"signingKeyFingerprint\"\n | \"encapsulationSuite\"\n | \"encapsulationPublicKey\"\n | \"encapsulationKeyFingerprint\";\n\ninterface PinDiagnostic {\n readonly userId: string;\n readonly formatVersion: number;\n readonly signingSuite: string;\n readonly signingKeyFingerprint: string;\n readonly encapsulationSuite: string;\n readonly encapsulationKeyFingerprint: string;\n readonly firstSeenAt: string;\n}\n\ninterface PinScope {\n readonly identityTrustDomain: string;\n readonly userId: string;\n}\n\nconst comparedPinFields: readonly ComparedPinField[] = [\n \"userId\",\n \"formatVersion\",\n \"signingSuite\",\n \"signingPublicKey\",\n \"signingKeyFingerprint\",\n \"encapsulationSuite\",\n \"encapsulationPublicKey\",\n \"encapsulationKeyFingerprint\",\n];\n\nconst requiredStringFields = [\n \"identityTrustDomain\",\n \"userId\",\n \"signingSuite\",\n \"signingPublicKey\",\n \"signingKeyFingerprint\",\n \"encapsulationSuite\",\n \"encapsulationPublicKey\",\n \"encapsulationKeyFingerprint\",\n] as const satisfies readonly (keyof TrustedUserIdentityPin)[];\n\nfunction pinDiagnostic(pin: TrustedUserIdentityPin): PinDiagnostic {\n return {\n userId: pin.userId,\n formatVersion: pin.formatVersion,\n signingSuite: pin.signingSuite,\n signingKeyFingerprint: pin.signingKeyFingerprint,\n encapsulationSuite: pin.encapsulationSuite,\n encapsulationKeyFingerprint: pin.encapsulationKeyFingerprint,\n firstSeenAt: pin.firstSeenAt,\n };\n}\n\nexport class TrustedUserIdentityPinMismatchError extends Error {\n readonly code = \"trusted_user_identity_pin_mismatch\";\n readonly identityTrustDomain: string;\n readonly userId: string;\n readonly existing: PinDiagnostic;\n readonly candidate: PinDiagnostic;\n readonly differingFields: readonly ComparedPinField[];\n\n constructor(\n existing: TrustedUserIdentityPin,\n candidate: TrustedUserIdentityPin,\n differingFields: readonly ComparedPinField[],\n ) {\n super(\n `Trusted identity pin mismatch for ${candidate.userId} in ${candidate.identityTrustDomain}: ${differingFields.join(\", \")}`,\n );\n this.name = \"TrustedUserIdentityPinMismatchError\";\n this.identityTrustDomain = candidate.identityTrustDomain;\n this.userId = candidate.userId;\n this.existing = pinDiagnostic(existing);\n this.candidate = pinDiagnostic(candidate);\n this.differingFields = differingFields;\n }\n}\n\nexport class TrustedUserIdentityPinCorruptError extends Error {\n readonly code = \"trusted_user_identity_pin_corrupt\";\n readonly identityTrustDomain: string;\n readonly userId: string;\n readonly reason: string;\n\n constructor(scope: PinScope, reason: string) {\n super(\n `Stored trusted identity pin is corrupt for ${scope.userId} in ${scope.identityTrustDomain}: ${reason}`,\n );\n this.name = \"TrustedUserIdentityPinCorruptError\";\n this.identityTrustDomain = scope.identityTrustDomain;\n this.userId = scope.userId;\n this.reason = reason;\n }\n}\n\nexport class TrustedUserIdentityPinInputError extends Error {\n readonly code = \"trusted_user_identity_pin_invalid_input\";\n readonly identityTrustDomain: string;\n readonly userId: string;\n readonly reason: string;\n\n constructor(scope: PinScope, reason: string) {\n super(`Invalid trusted identity pin candidate: ${reason}`);\n this.name = \"TrustedUserIdentityPinInputError\";\n this.identityTrustDomain = scope.identityTrustDomain;\n this.userId = scope.userId;\n this.reason = reason;\n }\n}\n\nfunction pinScope(value: unknown): PinScope {\n if (typeof value !== \"object\" || value === null) {\n return { identityTrustDomain: \"<invalid>\", userId: \"<invalid>\" };\n }\n const identityTrustDomain = Reflect.get(value, \"identityTrustDomain\");\n const userId = Reflect.get(value, \"userId\");\n return {\n identityTrustDomain:\n typeof identityTrustDomain === \"string\"\n ? identityTrustDomain\n : \"<invalid>\",\n userId: typeof userId === \"string\" ? userId : \"<invalid>\",\n };\n}\n\nfunction validationFailure(value: unknown): string | null {\n if (typeof value !== \"object\" || value === null) {\n return \"row must be an object\";\n }\n const formatVersion = Reflect.get(value, \"formatVersion\");\n if (formatVersion !== TRUSTED_USER_IDENTITY_FORMAT_VERSION) {\n return `formatVersion must be ${TRUSTED_USER_IDENTITY_FORMAT_VERSION}`;\n }\n for (const field of requiredStringFields) {\n const fieldValue = Reflect.get(value, field);\n if (typeof fieldValue !== \"string\" || fieldValue.trim().length === 0) {\n return `${field} must be a non-empty string`;\n }\n }\n if (\n Reflect.get(value, \"signingSuite\") !== TRUSTED_USER_IDENTITY_SIGNING_SUITE\n ) {\n return `signingSuite must be ${TRUSTED_USER_IDENTITY_SIGNING_SUITE}`;\n }\n if (\n Reflect.get(value, \"encapsulationSuite\") !==\n TRUSTED_USER_IDENTITY_ENCAPSULATION_SUITE\n ) {\n return `encapsulationSuite must be ${TRUSTED_USER_IDENTITY_ENCAPSULATION_SUITE}`;\n }\n const firstSeenAt = Reflect.get(value, \"firstSeenAt\");\n if (\n typeof firstSeenAt !== \"string\" ||\n !Number.isFinite(Date.parse(firstSeenAt))\n ) {\n return \"firstSeenAt must be a valid timestamp\";\n }\n return null;\n}\n\nfunction validatedStringField(\n value: object,\n field: (typeof requiredStringFields)[number] | \"firstSeenAt\",\n): string {\n const fieldValue = Reflect.get(value, field);\n if (typeof fieldValue !== \"string\") {\n throw new TypeError(`Validated pin field ${field} is not a string`);\n }\n return fieldValue;\n}\n\nfunction validatedFormatVersion(value: object): number {\n const formatVersion = Reflect.get(value, \"formatVersion\");\n if (typeof formatVersion !== \"number\") {\n throw new TypeError(\"Validated pin formatVersion is not a number\");\n }\n return formatVersion;\n}\n\nfunction assertCandidatePin(pin: TrustedUserIdentityPin): void {\n const failure = validationFailure(pin);\n if (failure) {\n throw new TrustedUserIdentityPinInputError(pinScope(pin), failure);\n }\n}\n\nfunction parseStoredPin(\n value: unknown,\n scope: PinScope,\n): TrustedUserIdentityPin {\n const failure = validationFailure(value);\n if (failure) {\n throw new TrustedUserIdentityPinCorruptError(scope, failure);\n }\n if (typeof value !== \"object\" || value === null) {\n throw new TrustedUserIdentityPinCorruptError(\n scope,\n \"row must be an object\",\n );\n }\n const pin: TrustedUserIdentityPin = {\n identityTrustDomain: validatedStringField(value, \"identityTrustDomain\"),\n userId: validatedStringField(value, \"userId\"),\n formatVersion: validatedFormatVersion(value),\n signingSuite: validatedStringField(value, \"signingSuite\"),\n signingPublicKey: validatedStringField(value, \"signingPublicKey\"),\n signingKeyFingerprint: validatedStringField(value, \"signingKeyFingerprint\"),\n encapsulationSuite: validatedStringField(value, \"encapsulationSuite\"),\n encapsulationPublicKey: validatedStringField(\n value,\n \"encapsulationPublicKey\",\n ),\n encapsulationKeyFingerprint: validatedStringField(\n value,\n \"encapsulationKeyFingerprint\",\n ),\n firstSeenAt: validatedStringField(value, \"firstSeenAt\"),\n };\n if (\n pin.identityTrustDomain !== scope.identityTrustDomain ||\n pin.userId !== scope.userId\n ) {\n throw new TrustedUserIdentityPinCorruptError(\n scope,\n \"primary-key scope does not match the requested pin\",\n );\n }\n return pin;\n}\n\nasync function selectStoredPin(\n db: ClientSQLiteDatabase | ClientSQLiteTransactionScope,\n scope: PinScope,\n): Promise<TrustedUserIdentityPin | null> {\n const rows = await db\n .select()\n .from(trustedUserIdentityPins)\n .where(\n and(\n eq(\n trustedUserIdentityPins.identityTrustDomain,\n scope.identityTrustDomain,\n ),\n eq(trustedUserIdentityPins.userId, scope.userId),\n ),\n )\n .limit(1);\n const row = rows[0];\n return row ? parseStoredPin(row, scope) : null;\n}\n\nfunction differingPinFields(\n existing: TrustedUserIdentityPin,\n candidate: TrustedUserIdentityPin,\n): readonly ComparedPinField[] {\n return comparedPinFields.filter(\n (field) => existing[field] !== candidate[field],\n );\n}\n\nexport async function loadTrustedUserIdentityPin(input: {\n readonly execSql: ExecSql;\n readonly identityTrustDomain: string;\n readonly userId: string;\n}): Promise<TrustedUserIdentityPin | null> {\n const scope: PinScope = {\n identityTrustDomain: input.identityTrustDomain,\n userId: input.userId,\n };\n await ensureSqlTables(input.execSql, trustedUserIdentityPinTables);\n return selectStoredPin(\n getClientSQLitePersistenceRuntime(input.execSql).db,\n scope,\n );\n}\n\n/**\n * The user a signing key is already bound to in this trust domain, if any.\n * Each key has at most one such user: the reverse binding is unique.\n */\nexport async function loadTrustedUserIdForSigningKey(input: {\n readonly execSql: ExecSql;\n readonly identityTrustDomain: string;\n readonly signingKeyFingerprint: string;\n}): Promise<string | null> {\n await ensureSqlTables(input.execSql, trustedUserIdentityPinTables);\n const rows = await getClientSQLitePersistenceRuntime(input.execSql)\n .db.select({ userId: trustedUserIdentityPins.userId })\n .from(trustedUserIdentityPins)\n .where(\n and(\n eq(\n trustedUserIdentityPins.identityTrustDomain,\n input.identityTrustDomain,\n ),\n eq(\n trustedUserIdentityPins.signingKeyFingerprint,\n input.signingKeyFingerprint,\n ),\n ),\n )\n .limit(1);\n return rows[0]?.userId ?? null;\n}\n\n/**\n * Atomically establishes or verifies a trust-on-first-use identity pin.\n *\n * The immediate transaction serializes first contact across SDK instances that\n * share the SQLite database. Conflict handling never replaces the existing\n * row: the winning pin is re-read and every identity field is compared before\n * this function returns.\n */\nexport async function compareOrInsertTrustedUserIdentityPin(input: {\n readonly execSql: ExecSql;\n readonly pin: TrustedUserIdentityPin;\n}): Promise<TrustedUserIdentityPin> {\n assertCandidatePin(input.pin);\n const runtime = getClientSQLitePersistenceRuntime(input.execSql);\n return runTrustedUserIdentityPinTransaction(async () => {\n await ensureSqlTables(input.execSql, trustedUserIdentityPinTables);\n return runtime.transaction(\n async (tx) => {\n const aliases = await tx\n .select()\n .from(trustedUserIdentityPins)\n .where(\n and(\n eq(\n trustedUserIdentityPins.identityTrustDomain,\n input.pin.identityTrustDomain,\n ),\n eq(\n trustedUserIdentityPins.signingKeyFingerprint,\n input.pin.signingKeyFingerprint,\n ),\n ),\n )\n .limit(1);\n const alias = aliases[0];\n if (alias && alias.userId !== input.pin.userId) {\n throw new TrustedUserIdentityPinMismatchError(\n parseStoredPin(alias, {\n identityTrustDomain: input.pin.identityTrustDomain,\n userId: alias.userId,\n }),\n input.pin,\n [\"userId\"],\n );\n }\n // Targets the primary key only: re-pinning the same user is a no-op,\n // but a write that would bind this key to a second user must fail at\n // the unique index rather than be silently skipped.\n await tx\n .insert(trustedUserIdentityPins)\n .values(input.pin)\n .onConflictDoNothing({\n target: [\n trustedUserIdentityPins.identityTrustDomain,\n trustedUserIdentityPins.userId,\n ],\n })\n .run();\n\n const stored = await selectStoredPin(tx, input.pin);\n if (!stored) {\n throw new TrustedUserIdentityPinCorruptError(\n input.pin,\n \"row is missing after compare-or-insert\",\n );\n }\n\n const differingFields = differingPinFields(stored, input.pin);\n if (differingFields.length > 0) {\n throw new TrustedUserIdentityPinMismatchError(\n stored,\n input.pin,\n differingFields,\n );\n }\n return stored;\n },\n { behavior: \"immediate\" },\n );\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"trustedUserIdentityPinPersistence.js","sourceRoot":"","sources":["trustedUserIdentityPinPersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAGL,iCAAiC,GAClC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAgB,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EACL,yCAAyC,EACzC,oCAAoC,EACpC,mCAAmC,GACpC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,oCAAoC,EAAE,MAAM,qCAAqC,CAAC;AAU3F,MAAM,cAAc,GAEhB,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAEtC,SAAS,kCAAkC,CACzC,KAAc;IAEd,OAAO,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,UAAU,CAAC;AACtE,CAAC;AAyCD,MAAM,iBAAiB,GAAgC;IACrD,QAAQ;IACR,eAAe;IACf,cAAc;IACd,kBAAkB;IAClB,uBAAuB;IACvB,oBAAoB;IACpB,wBAAwB;IACxB,6BAA6B;CAC9B,CAAC;AAEF,MAAM,oBAAoB,GAAG;IAC3B,qBAAqB;IACrB,QAAQ;IACR,cAAc;IACd,kBAAkB;IAClB,uBAAuB;IACvB,oBAAoB;IACpB,wBAAwB;IACxB,6BAA6B;CAC+B,CAAC;AAE/D,SAAS,aAAa,CAAC,GAA2B;IAChD,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,aAAa,EAAE,GAAG,CAAC,aAAa;QAChC,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,qBAAqB,EAAE,GAAG,CAAC,qBAAqB;QAChD,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;QAC1C,2BAA2B,EAAE,GAAG,CAAC,2BAA2B;QAC5D,WAAW,EAAE,GAAG,CAAC,WAAW;KAC7B,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,mCAAoC,SAAQ,KAAK;IACnD,IAAI,GAAG,oCAAoC,CAAC;IAC5C,mBAAmB,CAAS;IAC5B,MAAM,CAAS;IACf,QAAQ,CAAgB;IACxB,SAAS,CAAgB;IACzB,eAAe,CAA8B;IAEtD,YACE,QAAgC,EAChC,SAAiC,EACjC,eAA4C;QAE5C,KAAK,CACH,qCAAqC,SAAS,CAAC,MAAM,OAAO,SAAS,CAAC,mBAAmB,KAAK,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3H,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qCAAqC,CAAC;QAClD,IAAI,CAAC,mBAAmB,GAAG,SAAS,CAAC,mBAAmB,CAAC;QACzD,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,CAAC,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IACzC,CAAC;CACF;AAED,MAAM,OAAO,kCAAmC,SAAQ,KAAK;IAClD,IAAI,GAAG,mCAAmC,CAAC;IAC3C,mBAAmB,CAAS;IAC5B,MAAM,CAAS;IACf,MAAM,CAAS;IAExB,YAAY,KAAe,EAAE,MAAc;QACzC,KAAK,CACH,8CAA8C,KAAK,CAAC,MAAM,OAAO,KAAK,CAAC,mBAAmB,KAAK,MAAM,EAAE,CACxG,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,oCAAoC,CAAC;QACjD,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC,mBAAmB,CAAC;QACrD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,MAAM,OAAO,gCAAiC,SAAQ,KAAK;IAChD,IAAI,GAAG,yCAAyC,CAAC;IACjD,mBAAmB,CAAS;IAC5B,MAAM,CAAS;IACf,MAAM,CAAS;IAExB,YAAY,KAAe,EAAE,MAAc;QACzC,KAAK,CAAC,2CAA2C,MAAM,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,GAAG,kCAAkC,CAAC;QAC/C,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC,mBAAmB,CAAC;QACrD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACnE,CAAC;IACD,MAAM,mBAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC5C,OAAO;QACL,mBAAmB,EACjB,OAAO,mBAAmB,KAAK,QAAQ;YACrC,CAAC,CAAC,mBAAmB;YACrB,CAAC,CAAC,WAAW;QACjB,MAAM,EAAE,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW;KAC1D,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,uBAAuB,CAAC;IACjC,CAAC;IACD,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;IAC1D,IAAI,aAAa,KAAK,oCAAoC,EAAE,CAAC;QAC3D,OAAO,yBAAyB,oCAAoC,EAAE,CAAC;IACzE,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,oBAAoB,EAAE,CAAC;QACzC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC7C,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrE,OAAO,GAAG,KAAK,6BAA6B,CAAC;QAC/C,CAAC;IACH,CAAC;IACD,IACE,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,mCAAmC,EAC1E,CAAC;QACD,OAAO,wBAAwB,mCAAmC,EAAE,CAAC;IACvE,CAAC;IACD,IACE,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,oBAAoB,CAAC;QACxC,yCAAyC,EACzC,CAAC;QACD,OAAO,8BAA8B,yCAAyC,EAAE,CAAC;IACnF,CAAC;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IACtD,IACE,OAAO,WAAW,KAAK,QAAQ;QAC/B,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,EACzC,CAAC;QACD,OAAO,uCAAuC,CAAC;IACjD,CAAC;IACD,IAAI,CAAC,kCAAkC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC;QAC1E,OAAO,4CAA4C,CAAC;IACtD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,oBAAoB,CAC3B,KAAa,EACb,KAA4D;IAE5D,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC7C,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,IAAI,SAAS,CAAC,uBAAuB,KAAK,kBAAkB,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAa;IAC3C,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;IAC1D,IAAI,OAAO,aAAa,KAAK,QAAQ,EAAE,CAAC;QACtC,MAAM,IAAI,SAAS,CAAC,6CAA6C,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IACpD,IAAI,CAAC,kCAAkC,CAAC,UAAU,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,SAAS,CAAC,8CAA8C,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,kBAAkB,CAAC,GAA2B;IACrD,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,IAAI,gCAAgC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACrE,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CACrB,KAAc,EACd,KAAe;IAEf,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACzC,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,IAAI,kCAAkC,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,MAAM,IAAI,kCAAkC,CAC1C,KAAK,EACL,uBAAuB,CACxB,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAA2B;QAClC,mBAAmB,EAAE,oBAAoB,CAAC,KAAK,EAAE,qBAAqB,CAAC;QACvE,MAAM,EAAE,oBAAoB,CAAC,KAAK,EAAE,QAAQ,CAAC;QAC7C,aAAa,EAAE,sBAAsB,CAAC,KAAK,CAAC;QAC5C,YAAY,EAAE,oBAAoB,CAAC,KAAK,EAAE,cAAc,CAAC;QACzD,gBAAgB,EAAE,oBAAoB,CAAC,KAAK,EAAE,kBAAkB,CAAC;QACjE,qBAAqB,EAAE,oBAAoB,CAAC,KAAK,EAAE,uBAAuB,CAAC;QAC3E,kBAAkB,EAAE,oBAAoB,CAAC,KAAK,EAAE,oBAAoB,CAAC;QACrE,sBAAsB,EAAE,oBAAoB,CAC1C,KAAK,EACL,wBAAwB,CACzB;QACD,2BAA2B,EAAE,oBAAoB,CAC/C,KAAK,EACL,6BAA6B,CAC9B;QACD,WAAW,EAAE,oBAAoB,CAAC,KAAK,EAAE,aAAa,CAAC;QACvD,UAAU,EAAE,mBAAmB,CAAC,KAAK,CAAC;KACvC,CAAC;IACF,IACE,GAAG,CAAC,mBAAmB,KAAK,KAAK,CAAC,mBAAmB;QACrD,GAAG,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAC3B,CAAC;QACD,MAAM,IAAI,kCAAkC,CAC1C,KAAK,EACL,oDAAoD,CACrD,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,EAAuD,EACvD,KAAe;IAEf,MAAM,IAAI,GAAG,MAAM,EAAE;SAClB,MAAM,EAAE;SACR,IAAI,CAAC,uBAAuB,CAAC;SAC7B,KAAK,CACJ,GAAG,CACD,EAAE,CACA,uBAAuB,CAAC,mBAAmB,EAC3C,KAAK,CAAC,mBAAmB,CAC1B,EACD,EAAE,CAAC,uBAAuB,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CACjD,CACF;SACA,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAED,SAAS,kBAAkB,CACzB,QAAgC,EAChC,SAAiC;IAEjC,OAAO,iBAAiB,CAAC,MAAM,CAC7B,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC,KAAK,CAAC,CAChD,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,KAIhD;IACC,MAAM,KAAK,GAAa;QACtB,mBAAmB,EAAE,KAAK,CAAC,mBAAmB;QAC9C,MAAM,EAAE,KAAK,CAAC,MAAM;KACrB,CAAC;IACF,MAAM,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;IACnE,OAAO,eAAe,CACpB,iCAAiC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,EACnD,KAAK,CACN,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,KAIpD;IACC,MAAM,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;IACnE,MAAM,IAAI,GAAG,MAAM,iCAAiC,CAAC,KAAK,CAAC,OAAO,CAAC;SAChE,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,uBAAuB,CAAC,MAAM,EAAE,CAAC;SACrD,IAAI,CAAC,uBAAuB,CAAC;SAC7B,KAAK,CACJ,GAAG,CACD,EAAE,CACA,uBAAuB,CAAC,mBAAmB,EAC3C,KAAK,CAAC,mBAAmB,CAC1B,EACD,EAAE,CACA,uBAAuB,CAAC,qBAAqB,EAC7C,KAAK,CAAC,qBAAqB,CAC5B,CACF,CACF;SACA,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,IAAI,CAAC;AACjC,CAAC;AAED;;;;;GAKG;AACH,MAAM,IAAI,GAAG,uBAAuB,CAAC;AAErC,KAAK,UAAU,WAAW,CACxB,EAAgC,EAChC,GAA2B;IAE3B,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE;SAC1B,MAAM,EAAE;SACR,IAAI,CAAC,IAAI,CAAC;SACV,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,CAAC,mBAAmB,CAAC,EACrD,EAAE,CACA,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,EAC3B,EAAE,CAAC,IAAI,CAAC,qBAAqB,EAAE,GAAG,CAAC,qBAAqB,CAAC,CAC1D,CACF,CACF;SACA,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,IAAI,KAAK,IAAI,GAAG,EAAE,MAAM,KAAK,GAAG,CAAC,MAAM,IAAI,iBAAiB,CAAC,GAAG,CAAC;QAC/D,OAAO,IAAI,CAAC;IACd,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,cAAc,CAAC;IAC5B,IAAI,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACzE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,qCAAqC,CAAC,KAG3D;IACC,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;IACtB,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACxB,MAAM,OAAO,GAAG,iCAAiC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACjE,OAAO,oCAAoC,CAAC,KAAK,IAAI,EAAE;QACrD,MAAM,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;QACnE,2EAA2E;QAC3E,yEAAyE;QACzE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;QACvE,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAC1B,OAAO,OAAO,CAAC,WAAW,CACxB,KAAK,EAAE,EAAE,EAAE,EAAE;YACX,MAAM,OAAO,GAAG,MAAM,EAAE;iBACrB,MAAM,EAAE;iBACR,IAAI,CAAC,IAAI,CAAC;iBACV,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,CAAC,mBAAmB,CAAC,EACrD,EAAE,CAAC,IAAI,CAAC,qBAAqB,EAAE,GAAG,CAAC,qBAAqB,CAAC,CAC1D,CACF;iBACA,KAAK,CAAC,CAAC,CAAC,CAAC;YACZ,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC;gBACzC,MAAM,IAAI,mCAAmC,CAC3C,cAAc,CAAC,KAAK,EAAE;oBACpB,mBAAmB,EAAE,GAAG,CAAC,mBAAmB;oBAC5C,MAAM,EAAE,KAAK,CAAC,MAAM;iBACrB,CAAC,EACF,GAAG,EACH,CAAC,QAAQ,CAAC,CACX,CAAC;YACJ,CAAC;YACD,qEAAqE;YACrE,qEAAqE;YACrE,oDAAoD;YACpD,MAAM,EAAE;iBACL,MAAM,CAAC,IAAI,CAAC;iBACZ,MAAM,CAAC,GAAG,CAAC;iBACX,mBAAmB,CAAC;gBACnB,MAAM,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC;aAChD,CAAC;iBACD,GAAG,EAAE,CAAC;YAET,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,kCAAkC,CAC1C,GAAG,EACH,wCAAwC,CACzC,CAAC;YACJ,CAAC;YAED,MAAM,eAAe,GAAG,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YACxD,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC/B,MAAM,IAAI,mCAAmC,CAC3C,MAAM,EACN,GAAG,EACH,eAAe,CAChB,CAAC;YACJ,CAAC;YACD,IACE,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,EACnE,CAAC;gBACD,OAAO,MAAM,CAAC;YAChB,CAAC;YACD,MAAM,EAAE;iBACL,MAAM,CAAC,IAAI,CAAC;iBACZ,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;iBACnC,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,CAAC,mBAAmB,CAAC,EACrD,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAC5B,CACF;iBACA,GAAG,EAAE,CAAC;YACT,OAAO,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;QACnD,CAAC,EACD,EAAE,QAAQ,EAAE,WAAW,EAAE,CAC1B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { and, eq, or } from \"drizzle-orm\";\nimport {\n trustedUserIdentityPins,\n trustedUserIdentityPinTables,\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 {\n TRUSTED_USER_IDENTITY_ENCAPSULATION_SUITE,\n TRUSTED_USER_IDENTITY_FORMAT_VERSION,\n TRUSTED_USER_IDENTITY_SIGNING_SUITE,\n} from \"../trustedUserIdentity/types\";\nimport { runTrustedUserIdentityPinTransaction } from \"./trustedUserIdentityPinTransaction\";\n\n/**\n * How this device came to trust a pin. `self` is the user's own local keys,\n * `link` an independently exchanged identity link, and `observed` the first\n * server answer for a user already referenced by signed state. Only `self` and\n * `link` admit a user; provenance only ever rises, and never changes the keys.\n */\nexport type TrustedUserIdentityPinProvenance = \"self\" | \"link\" | \"observed\";\n\nconst provenanceRank: Readonly<\n Record<TrustedUserIdentityPinProvenance, number>\n> = { observed: 0, link: 1, self: 2 };\n\nfunction isTrustedUserIdentityPinProvenance(\n value: unknown,\n): value is TrustedUserIdentityPinProvenance {\n return value === \"self\" || value === \"link\" || value === \"observed\";\n}\n\nexport interface TrustedUserIdentityPin {\n readonly identityTrustDomain: string;\n readonly userId: string;\n readonly formatVersion: number;\n readonly signingSuite: string;\n readonly signingPublicKey: string;\n readonly signingKeyFingerprint: string;\n readonly encapsulationSuite: string;\n readonly encapsulationPublicKey: string;\n readonly encapsulationKeyFingerprint: string;\n readonly firstSeenAt: string;\n readonly provenance: TrustedUserIdentityPinProvenance;\n}\n\ntype ComparedPinField =\n | \"userId\"\n | \"formatVersion\"\n | \"signingSuite\"\n | \"signingPublicKey\"\n | \"signingKeyFingerprint\"\n | \"encapsulationSuite\"\n | \"encapsulationPublicKey\"\n | \"encapsulationKeyFingerprint\";\n\ninterface PinDiagnostic {\n readonly userId: string;\n readonly formatVersion: number;\n readonly signingSuite: string;\n readonly signingKeyFingerprint: string;\n readonly encapsulationSuite: string;\n readonly encapsulationKeyFingerprint: string;\n readonly firstSeenAt: string;\n}\n\ninterface PinScope {\n readonly identityTrustDomain: string;\n readonly userId: string;\n}\n\nconst comparedPinFields: readonly ComparedPinField[] = [\n \"userId\",\n \"formatVersion\",\n \"signingSuite\",\n \"signingPublicKey\",\n \"signingKeyFingerprint\",\n \"encapsulationSuite\",\n \"encapsulationPublicKey\",\n \"encapsulationKeyFingerprint\",\n];\n\nconst requiredStringFields = [\n \"identityTrustDomain\",\n \"userId\",\n \"signingSuite\",\n \"signingPublicKey\",\n \"signingKeyFingerprint\",\n \"encapsulationSuite\",\n \"encapsulationPublicKey\",\n \"encapsulationKeyFingerprint\",\n] as const satisfies readonly (keyof TrustedUserIdentityPin)[];\n\nfunction pinDiagnostic(pin: TrustedUserIdentityPin): PinDiagnostic {\n return {\n userId: pin.userId,\n formatVersion: pin.formatVersion,\n signingSuite: pin.signingSuite,\n signingKeyFingerprint: pin.signingKeyFingerprint,\n encapsulationSuite: pin.encapsulationSuite,\n encapsulationKeyFingerprint: pin.encapsulationKeyFingerprint,\n firstSeenAt: pin.firstSeenAt,\n };\n}\n\nexport class TrustedUserIdentityPinMismatchError extends Error {\n readonly code = \"trusted_user_identity_pin_mismatch\";\n readonly identityTrustDomain: string;\n readonly userId: string;\n readonly existing: PinDiagnostic;\n readonly candidate: PinDiagnostic;\n readonly differingFields: readonly ComparedPinField[];\n\n constructor(\n existing: TrustedUserIdentityPin,\n candidate: TrustedUserIdentityPin,\n differingFields: readonly ComparedPinField[],\n ) {\n super(\n `Trusted identity pin mismatch for ${candidate.userId} in ${candidate.identityTrustDomain}: ${differingFields.join(\", \")}`,\n );\n this.name = \"TrustedUserIdentityPinMismatchError\";\n this.identityTrustDomain = candidate.identityTrustDomain;\n this.userId = candidate.userId;\n this.existing = pinDiagnostic(existing);\n this.candidate = pinDiagnostic(candidate);\n this.differingFields = differingFields;\n }\n}\n\nexport class TrustedUserIdentityPinCorruptError extends Error {\n readonly code = \"trusted_user_identity_pin_corrupt\";\n readonly identityTrustDomain: string;\n readonly userId: string;\n readonly reason: string;\n\n constructor(scope: PinScope, reason: string) {\n super(\n `Stored trusted identity pin is corrupt for ${scope.userId} in ${scope.identityTrustDomain}: ${reason}`,\n );\n this.name = \"TrustedUserIdentityPinCorruptError\";\n this.identityTrustDomain = scope.identityTrustDomain;\n this.userId = scope.userId;\n this.reason = reason;\n }\n}\n\nexport class TrustedUserIdentityPinInputError extends Error {\n readonly code = \"trusted_user_identity_pin_invalid_input\";\n readonly identityTrustDomain: string;\n readonly userId: string;\n readonly reason: string;\n\n constructor(scope: PinScope, reason: string) {\n super(`Invalid trusted identity pin candidate: ${reason}`);\n this.name = \"TrustedUserIdentityPinInputError\";\n this.identityTrustDomain = scope.identityTrustDomain;\n this.userId = scope.userId;\n this.reason = reason;\n }\n}\n\nfunction pinScope(value: unknown): PinScope {\n if (typeof value !== \"object\" || value === null) {\n return { identityTrustDomain: \"<invalid>\", userId: \"<invalid>\" };\n }\n const identityTrustDomain = Reflect.get(value, \"identityTrustDomain\");\n const userId = Reflect.get(value, \"userId\");\n return {\n identityTrustDomain:\n typeof identityTrustDomain === \"string\"\n ? identityTrustDomain\n : \"<invalid>\",\n userId: typeof userId === \"string\" ? userId : \"<invalid>\",\n };\n}\n\nfunction validationFailure(value: unknown): string | null {\n if (typeof value !== \"object\" || value === null) {\n return \"row must be an object\";\n }\n const formatVersion = Reflect.get(value, \"formatVersion\");\n if (formatVersion !== TRUSTED_USER_IDENTITY_FORMAT_VERSION) {\n return `formatVersion must be ${TRUSTED_USER_IDENTITY_FORMAT_VERSION}`;\n }\n for (const field of requiredStringFields) {\n const fieldValue = Reflect.get(value, field);\n if (typeof fieldValue !== \"string\" || fieldValue.trim().length === 0) {\n return `${field} must be a non-empty string`;\n }\n }\n if (\n Reflect.get(value, \"signingSuite\") !== TRUSTED_USER_IDENTITY_SIGNING_SUITE\n ) {\n return `signingSuite must be ${TRUSTED_USER_IDENTITY_SIGNING_SUITE}`;\n }\n if (\n Reflect.get(value, \"encapsulationSuite\") !==\n TRUSTED_USER_IDENTITY_ENCAPSULATION_SUITE\n ) {\n return `encapsulationSuite must be ${TRUSTED_USER_IDENTITY_ENCAPSULATION_SUITE}`;\n }\n const firstSeenAt = Reflect.get(value, \"firstSeenAt\");\n if (\n typeof firstSeenAt !== \"string\" ||\n !Number.isFinite(Date.parse(firstSeenAt))\n ) {\n return \"firstSeenAt must be a valid timestamp\";\n }\n if (!isTrustedUserIdentityPinProvenance(Reflect.get(value, \"provenance\"))) {\n return \"provenance must be self, link, or observed\";\n }\n return null;\n}\n\nfunction validatedStringField(\n value: object,\n field: (typeof requiredStringFields)[number] | \"firstSeenAt\",\n): string {\n const fieldValue = Reflect.get(value, field);\n if (typeof fieldValue !== \"string\") {\n throw new TypeError(`Validated pin field ${field} is not a string`);\n }\n return fieldValue;\n}\n\nfunction validatedFormatVersion(value: object): number {\n const formatVersion = Reflect.get(value, \"formatVersion\");\n if (typeof formatVersion !== \"number\") {\n throw new TypeError(\"Validated pin formatVersion is not a number\");\n }\n return formatVersion;\n}\n\nfunction validatedProvenance(value: object): TrustedUserIdentityPinProvenance {\n const provenance = Reflect.get(value, \"provenance\");\n if (!isTrustedUserIdentityPinProvenance(provenance)) {\n throw new TypeError(\"Validated pin provenance is not a provenance\");\n }\n return provenance;\n}\n\nfunction assertCandidatePin(pin: TrustedUserIdentityPin): void {\n const failure = validationFailure(pin);\n if (failure) {\n throw new TrustedUserIdentityPinInputError(pinScope(pin), failure);\n }\n}\n\nfunction parseStoredPin(\n value: unknown,\n scope: PinScope,\n): TrustedUserIdentityPin {\n const failure = validationFailure(value);\n if (failure) {\n throw new TrustedUserIdentityPinCorruptError(scope, failure);\n }\n if (typeof value !== \"object\" || value === null) {\n throw new TrustedUserIdentityPinCorruptError(\n scope,\n \"row must be an object\",\n );\n }\n const pin: TrustedUserIdentityPin = {\n identityTrustDomain: validatedStringField(value, \"identityTrustDomain\"),\n userId: validatedStringField(value, \"userId\"),\n formatVersion: validatedFormatVersion(value),\n signingSuite: validatedStringField(value, \"signingSuite\"),\n signingPublicKey: validatedStringField(value, \"signingPublicKey\"),\n signingKeyFingerprint: validatedStringField(value, \"signingKeyFingerprint\"),\n encapsulationSuite: validatedStringField(value, \"encapsulationSuite\"),\n encapsulationPublicKey: validatedStringField(\n value,\n \"encapsulationPublicKey\",\n ),\n encapsulationKeyFingerprint: validatedStringField(\n value,\n \"encapsulationKeyFingerprint\",\n ),\n firstSeenAt: validatedStringField(value, \"firstSeenAt\"),\n provenance: validatedProvenance(value),\n };\n if (\n pin.identityTrustDomain !== scope.identityTrustDomain ||\n pin.userId !== scope.userId\n ) {\n throw new TrustedUserIdentityPinCorruptError(\n scope,\n \"primary-key scope does not match the requested pin\",\n );\n }\n return pin;\n}\n\nasync function selectStoredPin(\n db: ClientSQLiteDatabase | ClientSQLiteTransactionScope,\n scope: PinScope,\n): Promise<TrustedUserIdentityPin | null> {\n const rows = await db\n .select()\n .from(trustedUserIdentityPins)\n .where(\n and(\n eq(\n trustedUserIdentityPins.identityTrustDomain,\n scope.identityTrustDomain,\n ),\n eq(trustedUserIdentityPins.userId, scope.userId),\n ),\n )\n .limit(1);\n const row = rows[0];\n return row ? parseStoredPin(row, scope) : null;\n}\n\nfunction differingPinFields(\n existing: TrustedUserIdentityPin,\n candidate: TrustedUserIdentityPin,\n): readonly ComparedPinField[] {\n return comparedPinFields.filter(\n (field) => existing[field] !== candidate[field],\n );\n}\n\nexport async function loadTrustedUserIdentityPin(input: {\n readonly execSql: ExecSql;\n readonly identityTrustDomain: string;\n readonly userId: string;\n}): Promise<TrustedUserIdentityPin | null> {\n const scope: PinScope = {\n identityTrustDomain: input.identityTrustDomain,\n userId: input.userId,\n };\n await ensureSqlTables(input.execSql, trustedUserIdentityPinTables);\n return selectStoredPin(\n getClientSQLitePersistenceRuntime(input.execSql).db,\n scope,\n );\n}\n\n/**\n * The user a signing key is already bound to in this trust domain, if any.\n * Each key has at most one such user: the reverse binding is unique.\n */\nexport async function loadTrustedUserIdForSigningKey(input: {\n readonly execSql: ExecSql;\n readonly identityTrustDomain: string;\n readonly signingKeyFingerprint: string;\n}): Promise<string | null> {\n await ensureSqlTables(input.execSql, trustedUserIdentityPinTables);\n const rows = await getClientSQLitePersistenceRuntime(input.execSql)\n .db.select({ userId: trustedUserIdentityPins.userId })\n .from(trustedUserIdentityPins)\n .where(\n and(\n eq(\n trustedUserIdentityPins.identityTrustDomain,\n input.identityTrustDomain,\n ),\n eq(\n trustedUserIdentityPins.signingKeyFingerprint,\n input.signingKeyFingerprint,\n ),\n ),\n )\n .limit(1);\n return rows[0]?.userId ?? null;\n}\n\n/**\n * One read answers an exact repeat of a pin that alone holds its\n * key and ranks at or above the candidate; anything else returns null for the\n * write transaction. Pins are never deleted, keys never change, and\n * provenance only rises, so a match is what the transaction would return.\n */\nconst pins = trustedUserIdentityPins;\n\nasync function matchingPin(\n db: ClientSQLiteTransactionScope,\n pin: TrustedUserIdentityPin,\n): Promise<TrustedUserIdentityPin | null> {\n const [row, other] = await db\n .select()\n .from(pins)\n .where(\n and(\n eq(pins.identityTrustDomain, pin.identityTrustDomain),\n or(\n eq(pins.userId, pin.userId),\n eq(pins.signingKeyFingerprint, pin.signingKeyFingerprint),\n ),\n ),\n )\n .limit(2);\n if (other || row?.userId !== pin.userId || validationFailure(row))\n return null;\n const stored = parseStoredPin(row, pin);\n const rank = provenanceRank;\n if (differingPinFields(stored, pin).length > 0) return null;\n return rank[pin.provenance] <= rank[stored.provenance] ? stored : null;\n}\n\n/**\n * Atomically establishes or verifies a durable identity pin.\n *\n * The immediate transaction serializes first contact across SDK instances that\n * share the SQLite database. Conflict handling never replaces the existing\n * keys: the winning pin is re-read and every identity field is compared before\n * this function returns. When the keys match and the candidate's provenance\n * ranks higher (an identity link confirming an observed pin), only the\n * provenance is raised; it is never lowered.\n */\nexport async function compareOrInsertTrustedUserIdentityPin(input: {\n readonly execSql: ExecSql;\n readonly pin: TrustedUserIdentityPin;\n}): Promise<TrustedUserIdentityPin> {\n const { pin } = input;\n assertCandidatePin(pin);\n const runtime = getClientSQLitePersistenceRuntime(input.execSql);\n return runTrustedUserIdentityPinTransaction(async () => {\n await ensureSqlTables(input.execSql, trustedUserIdentityPinTables);\n // Like the write path's BEGIN, this read's BEGIN retries while any adapter\n // holds the connection's transaction, so it never sees uncommitted pins.\n const pinned = await runtime.transaction((tx) => matchingPin(tx, pin));\n if (pinned) return pinned;\n return runtime.transaction(\n async (tx) => {\n const aliases = await tx\n .select()\n .from(pins)\n .where(\n and(\n eq(pins.identityTrustDomain, pin.identityTrustDomain),\n eq(pins.signingKeyFingerprint, pin.signingKeyFingerprint),\n ),\n )\n .limit(1);\n const alias = aliases[0];\n if (alias && alias.userId !== pin.userId) {\n throw new TrustedUserIdentityPinMismatchError(\n parseStoredPin(alias, {\n identityTrustDomain: pin.identityTrustDomain,\n userId: alias.userId,\n }),\n pin,\n [\"userId\"],\n );\n }\n // Targets the primary key only: re-pinning the same user is a no-op,\n // but a write that would bind this key to a second user must fail at\n // the unique index rather than be silently skipped.\n await tx\n .insert(pins)\n .values(pin)\n .onConflictDoNothing({\n target: [pins.identityTrustDomain, pins.userId],\n })\n .run();\n\n const stored = await selectStoredPin(tx, pin);\n if (!stored) {\n throw new TrustedUserIdentityPinCorruptError(\n pin,\n \"row is missing after compare-or-insert\",\n );\n }\n\n const differingFields = differingPinFields(stored, pin);\n if (differingFields.length > 0) {\n throw new TrustedUserIdentityPinMismatchError(\n stored,\n pin,\n differingFields,\n );\n }\n if (\n provenanceRank[pin.provenance] <= provenanceRank[stored.provenance]\n ) {\n return stored;\n }\n await tx\n .update(pins)\n .set({ provenance: pin.provenance })\n .where(\n and(\n eq(pins.identityTrustDomain, pin.identityTrustDomain),\n eq(pins.userId, pin.userId),\n ),\n )\n .run();\n return { ...stored, provenance: pin.provenance };\n },\n { behavior: \"immediate\" },\n );\n });\n}\n"]}
|
|
@@ -6,7 +6,9 @@ import { containerTables, documentContainerProjection, documentContainerProjecti
|
|
|
6
6
|
import { containers } from "../sqlite/schema/containerSchema.js";
|
|
7
7
|
import { documentDiscoveryHeads, pendingDocumentDiscoveries, } from "../sqlite/schema/documentDiscoveryEvidenceSchema.js";
|
|
8
8
|
import { verifiedAccessManifestBundles as bundles, verifiedManifestBundleTables, } from "../sqlite/schema/verifiedManifestBundleSchema.js";
|
|
9
|
+
import { sqliteInsertBatchSize } from "../sqlite/sqliteBindBudget.js";
|
|
9
10
|
import { getClientSQLitePersistenceRuntime, } from "../sqlite/sqlitePersistenceRuntime.js";
|
|
11
|
+
import { excludedValue } from "../sqlite/sqliteUpsert.js";
|
|
10
12
|
import { ensureSqlTables } from "../sqlite/sqlSchema.js";
|
|
11
13
|
/**
|
|
12
14
|
* Bounds local bytes (a bundle is ~10–20 KB). Evicting the least recently
|
|
@@ -17,12 +19,16 @@ const STATEMENT_BATCH = 200;
|
|
|
17
19
|
function sha256Hex(text) {
|
|
18
20
|
return toFingerprint(new TextEncoder().encode(text));
|
|
19
21
|
}
|
|
20
|
-
|
|
22
|
+
const BUNDLE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(bundles);
|
|
23
|
+
function slices(values, size) {
|
|
21
24
|
const result = [];
|
|
22
|
-
for (let index = 0; index < values.length; index +=
|
|
23
|
-
result.push(values.slice(index, index +
|
|
25
|
+
for (let index = 0; index < values.length; index += size)
|
|
26
|
+
result.push(values.slice(index, index + size));
|
|
24
27
|
return result;
|
|
25
28
|
}
|
|
29
|
+
function batches(values) {
|
|
30
|
+
return slices(values, STATEMENT_BATCH);
|
|
31
|
+
}
|
|
26
32
|
async function pruneInTransaction(tx) {
|
|
27
33
|
const [row] = await tx.select({ count: sql `count(*)` }).from(bundles);
|
|
28
34
|
const excess = (row?.count ?? 0) - MAX_VERIFIED_MANIFEST_BUNDLES;
|
|
@@ -74,16 +80,27 @@ export async function writeVerifiedManifestBundlesInTransaction(tx, rows, verifi
|
|
|
74
80
|
.set({ verifiedAt })
|
|
75
81
|
.where(inArray(bundles.manifestHash, batch))
|
|
76
82
|
.run();
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
// A repeated hash keeps its last changed row, as sequential upserts would.
|
|
84
|
+
const changed = new Map(rows
|
|
85
|
+
.filter((row) => stored.get(row.manifestHash) !== row.bundleDigest)
|
|
86
|
+
.map((row) => [row.manifestHash, { ...row, verifiedAt }]));
|
|
87
|
+
for (const batch of slices([...changed.values()], BUNDLE_WRITE_BATCH_SIZE))
|
|
81
88
|
await tx
|
|
82
89
|
.insert(bundles)
|
|
83
|
-
.values(
|
|
84
|
-
.onConflictDoUpdate({
|
|
90
|
+
.values(batch)
|
|
91
|
+
.onConflictDoUpdate({
|
|
92
|
+
target: bundles.manifestHash,
|
|
93
|
+
set: {
|
|
94
|
+
organizationId: excludedValue(bundles.organizationId),
|
|
95
|
+
objectKind: excludedValue(bundles.objectKind),
|
|
96
|
+
objectId: excludedValue(bundles.objectId),
|
|
97
|
+
epoch: excludedValue(bundles.epoch),
|
|
98
|
+
bundleJson: excludedValue(bundles.bundleJson),
|
|
99
|
+
bundleDigest: excludedValue(bundles.bundleDigest),
|
|
100
|
+
verifiedAt: excludedValue(bundles.verifiedAt),
|
|
101
|
+
},
|
|
102
|
+
})
|
|
85
103
|
.run();
|
|
86
|
-
}
|
|
87
104
|
await pruneInTransaction(tx);
|
|
88
105
|
}
|
|
89
106
|
async function storedDigests(tx, manifestHashes) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verifiedManifestBundlePersistence.js","sourceRoot":"","sources":["verifiedManifestBundlePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n VerifiedManifestCitations,\n WriterProjectionTarget,\n} from \"@tearleads/api-client\";\nimport { toFingerprint } from \"@tearleads/crypto\";\nimport {\n type AccessManifestBundleWireResponse,\n isAccessManifestBundleWireResponse,\n} from \"@tearleads/validators/response\";\nimport {\n KNOWN_MANIFEST_HINT_LIMIT,\n MAX_CONTAINER_PATH_LENGTH,\n} from \"@tearleads/validators/util\";\nimport { and, asc, desc, eq, inArray, sql } from \"drizzle-orm\";\nimport {\n containerTables,\n documentContainerProjection,\n documentContainerProjectionTables,\n} from \"../sqlite/schema\";\nimport { containers } from \"../sqlite/schema/containerSchema\";\nimport {\n documentDiscoveryHeads,\n pendingDocumentDiscoveries,\n} from \"../sqlite/schema/documentDiscoveryEvidenceSchema\";\nimport {\n verifiedAccessManifestBundles as bundles,\n verifiedManifestBundleTables,\n} from \"../sqlite/schema/verifiedManifestBundleSchema\";\nimport {\n type ClientSQLiteDatabase,\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../sqlite/sqlSchema\";\n\n/**\n * Bounds local bytes (a bundle is ~10–20 KB). Evicting the least recently\n * verified rows only makes a later read carry those bundles again.\n */\nexport const MAX_VERIFIED_MANIFEST_BUNDLES = 1024;\nconst STATEMENT_BATCH = 200;\n\nexport interface VerifiedManifestBundle {\n readonly bundle: AccessManifestBundleWireResponse;\n readonly checkpoint: {\n readonly objectKind: string;\n readonly organizationId: string;\n readonly objectId: string;\n readonly epoch: number;\n };\n}\n\nfunction sha256Hex(text: string): Promise<string> {\n return toFingerprint(new TextEncoder().encode(text));\n}\n\nfunction batches<T>(values: readonly T[]): T[][] {\n const result: T[][] = [];\n for (let index = 0; index < values.length; index += STATEMENT_BATCH)\n result.push(values.slice(index, index + STATEMENT_BATCH));\n return result;\n}\n\nasync function pruneInTransaction(\n tx: ClientSQLiteTransactionScope,\n): Promise<void> {\n const [row] = await tx.select({ count: sql<number>`count(*)` }).from(bundles);\n const excess = (row?.count ?? 0) - MAX_VERIFIED_MANIFEST_BUNDLES;\n if (excess <= 0) return;\n const evicted = await tx\n .select({ manifestHash: bundles.manifestHash })\n .from(bundles)\n .orderBy(asc(bundles.verifiedAt), asc(bundles.manifestHash))\n .limit(excess);\n for (const batch of batches(evicted.map((entry) => entry.manifestHash)))\n await tx.delete(bundles).where(inArray(bundles.manifestHash, batch)).run();\n}\n\n/** A verified bundle's exact JSON and that JSON's digest. */\nexport type VerifiedManifestBundleRow = Omit<\n typeof bundles.$inferInsert,\n \"verifiedAt\"\n>;\n\n/**\n * Prepares rows for bundles that just passed projection verification. The\n * digests are computed here, before the checkpoint transaction that writes\n * them begins.\n */\nexport function verifiedManifestBundleRows(\n entries: readonly VerifiedManifestBundle[],\n): Promise<VerifiedManifestBundleRow[]> {\n const unique = new Map(\n entries.map((entry) => [entry.bundle.manifestHash, entry]),\n );\n return Promise.all(\n [...unique.values()].map(async ({ bundle, checkpoint }) => {\n const bundleJson = JSON.stringify(bundle);\n return {\n manifestHash: bundle.manifestHash,\n organizationId: checkpoint.organizationId,\n objectKind: checkpoint.objectKind,\n objectId: checkpoint.objectId,\n epoch: checkpoint.epoch,\n bundleJson,\n bundleDigest: await sha256Hex(bundleJson),\n };\n }),\n );\n}\n\n/**\n * Records verified bundles inside the transaction that finalizes their\n * projection's checkpoints, so they commit or roll back with it and no cache\n * work outlives the verification. Freshly verified bytes replace any stored\n * copy.\n */\nexport async function writeVerifiedManifestBundlesInTransaction(\n tx: ClientSQLiteTransactionScope,\n rows: readonly VerifiedManifestBundleRow[],\n verifiedAt: string,\n): Promise<void> {\n if (rows.length === 0) return;\n const stored = await storedDigests(\n tx,\n rows.map((row) => row.manifestHash),\n );\n // An identical stored row only needs its recency refreshed.\n const unchanged = rows.filter(\n (row) => stored.get(row.manifestHash) === row.bundleDigest,\n );\n for (const batch of batches(unchanged.map((row) => row.manifestHash)))\n await tx\n .update(bundles)\n .set({ verifiedAt })\n .where(inArray(bundles.manifestHash, batch))\n .run();\n for (const row of rows) {\n if (stored.get(row.manifestHash) === row.bundleDigest) continue;\n const verified = { ...row, verifiedAt };\n await tx\n .insert(bundles)\n .values(verified)\n .onConflictDoUpdate({ target: bundles.manifestHash, set: verified })\n .run();\n }\n await pruneInTransaction(tx);\n}\n\nasync function storedDigests(\n tx: ClientSQLiteTransactionScope,\n manifestHashes: readonly string[],\n): Promise<Map<string, string>> {\n const digests = new Map<string, string>();\n for (const batch of batches(manifestHashes)) {\n const rows = await tx\n .select({\n bundleDigest: bundles.bundleDigest,\n manifestHash: bundles.manifestHash,\n })\n .from(bundles)\n .where(inArray(bundles.manifestHash, batch));\n for (const row of rows) digests.set(row.manifestHash, row.bundleDigest);\n }\n return digests;\n}\n\n/** The requested object first, then each container whose lineage it cites. */\nasync function containerLineage(\n db: ClientSQLiteDatabase,\n containerId: string,\n): Promise<string[]> {\n const lineage = [containerId];\n while (lineage.length < MAX_CONTAINER_PATH_LENGTH) {\n const [row] = await db\n .select({ parentId: containers.parentId })\n .from(containers)\n .where(eq(containers.id, lineage.at(-1) ?? \"\"))\n .limit(1);\n const parentId = row?.parentId;\n if (!parentId || lineage.includes(parentId)) break;\n lineage.push(parentId);\n }\n return lineage;\n}\n\nasync function citedObjects(\n db: ClientSQLiteDatabase,\n target: WriterProjectionTarget,\n): Promise<{ readonly kind: string; readonly id: string }[]> {\n if (target.kind === \"container\")\n return (await containerLineage(db, target.id)).map((id) => ({\n kind: \"container\",\n id,\n }));\n // Linked containers, containers listing the document before its signed\n // placement is checked, and a container whose metadata document it is.\n const placements = [\n ...(await db\n .select({ containerId: documentContainerProjection.containerId })\n .from(documentContainerProjection)\n .where(eq(documentContainerProjection.documentId, target.id))),\n ...(await db\n .select({ containerId: pendingDocumentDiscoveries.containerId })\n .from(pendingDocumentDiscoveries)\n .where(eq(pendingDocumentDiscoveries.documentId, target.id))),\n ...(await db\n .select({ containerId: containers.id })\n .from(containers)\n .where(eq(containers.metadataDocumentId, target.id))),\n ];\n const containerIds = new Set<string>();\n for (const { containerId } of placements)\n if (containerId)\n for (const id of await containerLineage(db, containerId))\n containerIds.add(id);\n return [\n { kind: \"document\", id: target.id },\n ...[...containerIds].map((id) => ({ kind: \"container\", id })),\n ];\n}\n\n/**\n * The one organization every local source places the read in. Verified\n * ownership (stored verified bundles of the target and of the containers on\n * its walk, and a document's verified discovery head) and local placement\n * (listed container rows reached through links, pending listings, metadata,\n * or parents) must all agree: any disagreement, or no source at all, yields\n * null, so the read is sent with no hints rather than another\n * organization's.\n */\nasync function targetOrganization(\n db: ClientSQLiteDatabase,\n target: WriterProjectionTarget,\n objects: readonly { readonly kind: string; readonly id: string }[],\n): Promise<string | null> {\n const containerIds = objects\n .filter((object) => object.kind === \"container\")\n .map((object) => object.id);\n const listed =\n containerIds.length === 0\n ? []\n : await db\n .select({ organizationId: containers.organizationId })\n .from(containers)\n .where(inArray(containers.id, containerIds));\n const verifiedContainers =\n containerIds.length === 0\n ? []\n : await db\n .select({ organizationId: bundles.organizationId })\n .from(bundles)\n .where(\n and(\n eq(bundles.objectKind, \"container\"),\n inArray(bundles.objectId, containerIds),\n ),\n );\n const verifiedTarget = await db\n .select({ organizationId: bundles.organizationId })\n .from(bundles)\n .where(\n and(eq(bundles.objectKind, target.kind), eq(bundles.objectId, target.id)),\n );\n const discovered =\n target.kind === \"document\"\n ? await db\n .select({ organizationId: documentDiscoveryHeads.organizationId })\n .from(documentDiscoveryHeads)\n .where(eq(documentDiscoveryHeads.documentId, target.id))\n : [];\n const sources = [listed, verifiedContainers, verifiedTarget, discovered];\n const organizations = new Set(\n sources.flat().map((row) => row.organizationId),\n );\n const [organizationId] = organizations;\n return organizations.size === 1 && organizationId ? organizationId : null;\n}\n\ninterface StoredBundleRow {\n readonly bundleDigest: string;\n readonly bundleJson: string;\n readonly manifestHash: string;\n}\n\nconst storedColumns = {\n bundleDigest: bundles.bundleDigest,\n bundleJson: bundles.bundleJson,\n manifestHash: bundles.manifestHash,\n};\n\nasync function readStoredBundle(\n row: StoredBundleRow,\n): Promise<AccessManifestBundleWireResponse | null> {\n if ((await sha256Hex(row.bundleJson)) !== row.bundleDigest) return null;\n try {\n const bundle: unknown = JSON.parse(row.bundleJson);\n return isAccessManifestBundleWireResponse(bundle) &&\n bundle.manifestHash === row.manifestHash\n ? bundle\n : null;\n } catch {\n return null;\n }\n}\n\nfunction objectRows(\n db: ClientSQLiteDatabase,\n organizationId: string,\n object: { readonly kind: string; readonly id: string },\n limit: number,\n) {\n return db\n .select(storedColumns)\n .from(bundles)\n .where(\n and(\n eq(bundles.organizationId, organizationId),\n eq(bundles.objectKind, object.kind),\n eq(bundles.objectId, object.id),\n ),\n )\n .orderBy(desc(bundles.epoch))\n .limit(limit);\n}\n\nfunction recentRows(\n db: ClientSQLiteDatabase,\n organizationId: string,\n limit: number,\n) {\n return db\n .select(storedColumns)\n .from(bundles)\n .where(eq(bundles.organizationId, organizationId))\n .orderBy(desc(bundles.verifiedAt), asc(bundles.manifestHash))\n .limit(limit);\n}\n\n/**\n * Verified bundles of the target's object and of the containers its\n * projection walks, newest epochs first; then the most recently verified rows\n * of its organization, since a just-verified parent is the likeliest part of\n * a new child's projection. Citations never leave the target's organization:\n * an object whose organization the device cannot place cites nothing.\n * Damaged rows are dropped.\n */\nexport async function loadVerifiedManifestCitations(\n execSql: ExecSql,\n target: WriterProjectionTarget,\n limit = KNOWN_MANIFEST_HINT_LIMIT,\n): Promise<AccessManifestBundleWireResponse[]> {\n await ensureSqlTables(execSql, [\n ...verifiedManifestBundleTables,\n ...containerTables,\n ...documentContainerProjectionTables,\n ]);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const cited = new Map<string, AccessManifestBundleWireResponse>();\n const damaged: string[] = [];\n const collect = async (rows: readonly StoredBundleRow[]) => {\n for (const row of rows) {\n if (cited.size >= limit || cited.has(row.manifestHash)) continue;\n const bundle = await readStoredBundle(row);\n if (bundle) cited.set(row.manifestHash, bundle);\n else damaged.push(row.manifestHash);\n }\n };\n const objects = await citedObjects(db, target);\n const organizationId = await targetOrganization(db, target, objects);\n if (organizationId === null) return [];\n for (const object of objects)\n if (cited.size < limit)\n await collect(\n await objectRows(db, organizationId, object, limit - cited.size),\n );\n if (cited.size < limit)\n await collect(await recentRows(db, organizationId, limit + cited.size));\n if (damaged.length > 0)\n await getClientSQLitePersistenceRuntime(execSql)\n .runMutation((handle) =>\n handle\n .delete(bundles)\n .where(inArray(bundles.manifestHash, damaged))\n .run(),\n )\n .catch(() => undefined);\n return [...cited.values()];\n}\n\n/** Cites from whichever database the signed-in identity currently uses. */\nexport function createVerifiedManifestCitations(\n resolveExecSql: () => ExecSql | null,\n): VerifiedManifestCitations {\n return {\n cite: async (target) => {\n const execSql = resolveExecSql();\n return execSql ? loadVerifiedManifestCitations(execSql, target) : [];\n },\n };\n}\n\n/** Drops an organization's verified bundles when a remote reset forgets it. */\nexport async function deleteVerifiedManifestBundlesForOrganization(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<void> {\n await tx\n .delete(bundles)\n .where(eq(bundles.organizationId, organizationId))\n .run();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"verifiedManifestBundlePersistence.js","sourceRoot":"","sources":["verifiedManifestBundlePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n VerifiedManifestCitations,\n WriterProjectionTarget,\n} from \"@tearleads/api-client\";\nimport { toFingerprint } from \"@tearleads/crypto\";\nimport {\n type AccessManifestBundleWireResponse,\n isAccessManifestBundleWireResponse,\n} from \"@tearleads/validators/response\";\nimport {\n KNOWN_MANIFEST_HINT_LIMIT,\n MAX_CONTAINER_PATH_LENGTH,\n} from \"@tearleads/validators/util\";\nimport { and, asc, desc, eq, inArray, sql } from \"drizzle-orm\";\nimport {\n containerTables,\n documentContainerProjection,\n documentContainerProjectionTables,\n} from \"../sqlite/schema\";\nimport { containers } from \"../sqlite/schema/containerSchema\";\nimport {\n documentDiscoveryHeads,\n pendingDocumentDiscoveries,\n} from \"../sqlite/schema/documentDiscoveryEvidenceSchema\";\nimport {\n verifiedAccessManifestBundles as bundles,\n verifiedManifestBundleTables,\n} from \"../sqlite/schema/verifiedManifestBundleSchema\";\nimport { sqliteInsertBatchSize } 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\";\n\n/**\n * Bounds local bytes (a bundle is ~10–20 KB). Evicting the least recently\n * verified rows only makes a later read carry those bundles again.\n */\nexport const MAX_VERIFIED_MANIFEST_BUNDLES = 1024;\nconst STATEMENT_BATCH = 200;\n\nexport interface VerifiedManifestBundle {\n readonly bundle: AccessManifestBundleWireResponse;\n readonly checkpoint: {\n readonly objectKind: string;\n readonly organizationId: string;\n readonly objectId: string;\n readonly epoch: number;\n };\n}\n\nfunction sha256Hex(text: string): Promise<string> {\n return toFingerprint(new TextEncoder().encode(text));\n}\n\nconst BUNDLE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(bundles);\n\nfunction slices<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\nfunction batches<T>(values: readonly T[]): T[][] {\n return slices(values, STATEMENT_BATCH);\n}\n\nasync function pruneInTransaction(\n tx: ClientSQLiteTransactionScope,\n): Promise<void> {\n const [row] = await tx.select({ count: sql<number>`count(*)` }).from(bundles);\n const excess = (row?.count ?? 0) - MAX_VERIFIED_MANIFEST_BUNDLES;\n if (excess <= 0) return;\n const evicted = await tx\n .select({ manifestHash: bundles.manifestHash })\n .from(bundles)\n .orderBy(asc(bundles.verifiedAt), asc(bundles.manifestHash))\n .limit(excess);\n for (const batch of batches(evicted.map((entry) => entry.manifestHash)))\n await tx.delete(bundles).where(inArray(bundles.manifestHash, batch)).run();\n}\n\n/** A verified bundle's exact JSON and that JSON's digest. */\nexport type VerifiedManifestBundleRow = Omit<\n typeof bundles.$inferInsert,\n \"verifiedAt\"\n>;\n\n/**\n * Prepares rows for bundles that just passed projection verification. The\n * digests are computed here, before the checkpoint transaction that writes\n * them begins.\n */\nexport function verifiedManifestBundleRows(\n entries: readonly VerifiedManifestBundle[],\n): Promise<VerifiedManifestBundleRow[]> {\n const unique = new Map(\n entries.map((entry) => [entry.bundle.manifestHash, entry]),\n );\n return Promise.all(\n [...unique.values()].map(async ({ bundle, checkpoint }) => {\n const bundleJson = JSON.stringify(bundle);\n return {\n manifestHash: bundle.manifestHash,\n organizationId: checkpoint.organizationId,\n objectKind: checkpoint.objectKind,\n objectId: checkpoint.objectId,\n epoch: checkpoint.epoch,\n bundleJson,\n bundleDigest: await sha256Hex(bundleJson),\n };\n }),\n );\n}\n\n/**\n * Records verified bundles inside the transaction that finalizes their\n * projection's checkpoints, so they commit or roll back with it and no cache\n * work outlives the verification. Freshly verified bytes replace any stored\n * copy.\n */\nexport async function writeVerifiedManifestBundlesInTransaction(\n tx: ClientSQLiteTransactionScope,\n rows: readonly VerifiedManifestBundleRow[],\n verifiedAt: string,\n): Promise<void> {\n if (rows.length === 0) return;\n const stored = await storedDigests(\n tx,\n rows.map((row) => row.manifestHash),\n );\n // An identical stored row only needs its recency refreshed.\n const unchanged = rows.filter(\n (row) => stored.get(row.manifestHash) === row.bundleDigest,\n );\n for (const batch of batches(unchanged.map((row) => row.manifestHash)))\n await tx\n .update(bundles)\n .set({ verifiedAt })\n .where(inArray(bundles.manifestHash, batch))\n .run();\n // A repeated hash keeps its last changed row, as sequential upserts would.\n const changed = new Map(\n rows\n .filter((row) => stored.get(row.manifestHash) !== row.bundleDigest)\n .map((row) => [row.manifestHash, { ...row, verifiedAt }]),\n );\n for (const batch of slices([...changed.values()], BUNDLE_WRITE_BATCH_SIZE))\n await tx\n .insert(bundles)\n .values(batch)\n .onConflictDoUpdate({\n target: bundles.manifestHash,\n set: {\n organizationId: excludedValue(bundles.organizationId),\n objectKind: excludedValue(bundles.objectKind),\n objectId: excludedValue(bundles.objectId),\n epoch: excludedValue(bundles.epoch),\n bundleJson: excludedValue(bundles.bundleJson),\n bundleDigest: excludedValue(bundles.bundleDigest),\n verifiedAt: excludedValue(bundles.verifiedAt),\n },\n })\n .run();\n await pruneInTransaction(tx);\n}\n\nasync function storedDigests(\n tx: ClientSQLiteTransactionScope,\n manifestHashes: readonly string[],\n): Promise<Map<string, string>> {\n const digests = new Map<string, string>();\n for (const batch of batches(manifestHashes)) {\n const rows = await tx\n .select({\n bundleDigest: bundles.bundleDigest,\n manifestHash: bundles.manifestHash,\n })\n .from(bundles)\n .where(inArray(bundles.manifestHash, batch));\n for (const row of rows) digests.set(row.manifestHash, row.bundleDigest);\n }\n return digests;\n}\n\n/** The requested object first, then each container whose lineage it cites. */\nasync function containerLineage(\n db: ClientSQLiteDatabase,\n containerId: string,\n): Promise<string[]> {\n const lineage = [containerId];\n while (lineage.length < MAX_CONTAINER_PATH_LENGTH) {\n const [row] = await db\n .select({ parentId: containers.parentId })\n .from(containers)\n .where(eq(containers.id, lineage.at(-1) ?? \"\"))\n .limit(1);\n const parentId = row?.parentId;\n if (!parentId || lineage.includes(parentId)) break;\n lineage.push(parentId);\n }\n return lineage;\n}\n\nasync function citedObjects(\n db: ClientSQLiteDatabase,\n target: WriterProjectionTarget,\n): Promise<{ readonly kind: string; readonly id: string }[]> {\n if (target.kind === \"container\")\n return (await containerLineage(db, target.id)).map((id) => ({\n kind: \"container\",\n id,\n }));\n // Linked containers, containers listing the document before its signed\n // placement is checked, and a container whose metadata document it is.\n const placements = [\n ...(await db\n .select({ containerId: documentContainerProjection.containerId })\n .from(documentContainerProjection)\n .where(eq(documentContainerProjection.documentId, target.id))),\n ...(await db\n .select({ containerId: pendingDocumentDiscoveries.containerId })\n .from(pendingDocumentDiscoveries)\n .where(eq(pendingDocumentDiscoveries.documentId, target.id))),\n ...(await db\n .select({ containerId: containers.id })\n .from(containers)\n .where(eq(containers.metadataDocumentId, target.id))),\n ];\n const containerIds = new Set<string>();\n for (const { containerId } of placements)\n if (containerId)\n for (const id of await containerLineage(db, containerId))\n containerIds.add(id);\n return [\n { kind: \"document\", id: target.id },\n ...[...containerIds].map((id) => ({ kind: \"container\", id })),\n ];\n}\n\n/**\n * The one organization every local source places the read in. Verified\n * ownership (stored verified bundles of the target and of the containers on\n * its walk, and a document's verified discovery head) and local placement\n * (listed container rows reached through links, pending listings, metadata,\n * or parents) must all agree: any disagreement, or no source at all, yields\n * null, so the read is sent with no hints rather than another\n * organization's.\n */\nasync function targetOrganization(\n db: ClientSQLiteDatabase,\n target: WriterProjectionTarget,\n objects: readonly { readonly kind: string; readonly id: string }[],\n): Promise<string | null> {\n const containerIds = objects\n .filter((object) => object.kind === \"container\")\n .map((object) => object.id);\n const listed =\n containerIds.length === 0\n ? []\n : await db\n .select({ organizationId: containers.organizationId })\n .from(containers)\n .where(inArray(containers.id, containerIds));\n const verifiedContainers =\n containerIds.length === 0\n ? []\n : await db\n .select({ organizationId: bundles.organizationId })\n .from(bundles)\n .where(\n and(\n eq(bundles.objectKind, \"container\"),\n inArray(bundles.objectId, containerIds),\n ),\n );\n const verifiedTarget = await db\n .select({ organizationId: bundles.organizationId })\n .from(bundles)\n .where(\n and(eq(bundles.objectKind, target.kind), eq(bundles.objectId, target.id)),\n );\n const discovered =\n target.kind === \"document\"\n ? await db\n .select({ organizationId: documentDiscoveryHeads.organizationId })\n .from(documentDiscoveryHeads)\n .where(eq(documentDiscoveryHeads.documentId, target.id))\n : [];\n const sources = [listed, verifiedContainers, verifiedTarget, discovered];\n const organizations = new Set(\n sources.flat().map((row) => row.organizationId),\n );\n const [organizationId] = organizations;\n return organizations.size === 1 && organizationId ? organizationId : null;\n}\n\ninterface StoredBundleRow {\n readonly bundleDigest: string;\n readonly bundleJson: string;\n readonly manifestHash: string;\n}\n\nconst storedColumns = {\n bundleDigest: bundles.bundleDigest,\n bundleJson: bundles.bundleJson,\n manifestHash: bundles.manifestHash,\n};\n\nasync function readStoredBundle(\n row: StoredBundleRow,\n): Promise<AccessManifestBundleWireResponse | null> {\n if ((await sha256Hex(row.bundleJson)) !== row.bundleDigest) return null;\n try {\n const bundle: unknown = JSON.parse(row.bundleJson);\n return isAccessManifestBundleWireResponse(bundle) &&\n bundle.manifestHash === row.manifestHash\n ? bundle\n : null;\n } catch {\n return null;\n }\n}\n\nfunction objectRows(\n db: ClientSQLiteDatabase,\n organizationId: string,\n object: { readonly kind: string; readonly id: string },\n limit: number,\n) {\n return db\n .select(storedColumns)\n .from(bundles)\n .where(\n and(\n eq(bundles.organizationId, organizationId),\n eq(bundles.objectKind, object.kind),\n eq(bundles.objectId, object.id),\n ),\n )\n .orderBy(desc(bundles.epoch))\n .limit(limit);\n}\n\nfunction recentRows(\n db: ClientSQLiteDatabase,\n organizationId: string,\n limit: number,\n) {\n return db\n .select(storedColumns)\n .from(bundles)\n .where(eq(bundles.organizationId, organizationId))\n .orderBy(desc(bundles.verifiedAt), asc(bundles.manifestHash))\n .limit(limit);\n}\n\n/**\n * Verified bundles of the target's object and of the containers its\n * projection walks, newest epochs first; then the most recently verified rows\n * of its organization, since a just-verified parent is the likeliest part of\n * a new child's projection. Citations never leave the target's organization:\n * an object whose organization the device cannot place cites nothing.\n * Damaged rows are dropped.\n */\nexport async function loadVerifiedManifestCitations(\n execSql: ExecSql,\n target: WriterProjectionTarget,\n limit = KNOWN_MANIFEST_HINT_LIMIT,\n): Promise<AccessManifestBundleWireResponse[]> {\n await ensureSqlTables(execSql, [\n ...verifiedManifestBundleTables,\n ...containerTables,\n ...documentContainerProjectionTables,\n ]);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const cited = new Map<string, AccessManifestBundleWireResponse>();\n const damaged: string[] = [];\n const collect = async (rows: readonly StoredBundleRow[]) => {\n for (const row of rows) {\n if (cited.size >= limit || cited.has(row.manifestHash)) continue;\n const bundle = await readStoredBundle(row);\n if (bundle) cited.set(row.manifestHash, bundle);\n else damaged.push(row.manifestHash);\n }\n };\n const objects = await citedObjects(db, target);\n const organizationId = await targetOrganization(db, target, objects);\n if (organizationId === null) return [];\n for (const object of objects)\n if (cited.size < limit)\n await collect(\n await objectRows(db, organizationId, object, limit - cited.size),\n );\n if (cited.size < limit)\n await collect(await recentRows(db, organizationId, limit + cited.size));\n if (damaged.length > 0)\n await getClientSQLitePersistenceRuntime(execSql)\n .runMutation((handle) =>\n handle\n .delete(bundles)\n .where(inArray(bundles.manifestHash, damaged))\n .run(),\n )\n .catch(() => undefined);\n return [...cited.values()];\n}\n\n/** Cites from whichever database the signed-in identity currently uses. */\nexport function createVerifiedManifestCitations(\n resolveExecSql: () => ExecSql | null,\n): VerifiedManifestCitations {\n return {\n cite: async (target) => {\n const execSql = resolveExecSql();\n return execSql ? loadVerifiedManifestCitations(execSql, target) : [];\n },\n };\n}\n\n/** Drops an organization's verified bundles when a remote reset forgets it. */\nexport async function deleteVerifiedManifestBundlesForOrganization(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<void> {\n await tx\n .delete(bundles)\n .where(eq(bundles.organizationId, organizationId))\n .run();\n}\n"]}
|