@tearleads/client-sdk 0.1.116 → 0.1.118
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/data/keyingProjectionVerification/checkpoints/checkpointContext.d.ts +8 -1
- package/data/keyingProjectionVerification/checkpoints/checkpointContext.js +15 -0
- package/data/keyingProjectionVerification/checkpoints/checkpointContext.js.map +1 -1
- package/data/keyingProjectionVerification/container/containerProjectionVerification.js +3 -1
- package/data/keyingProjectionVerification/container/containerProjectionVerification.js.map +1 -1
- package/data/keyingProjectionVerification/document/documentProjectionVerification.js +2 -1
- package/data/keyingProjectionVerification/document/documentProjectionVerification.js.map +1 -1
- package/data/persistence/container-contents/containerContentsPersistence.js +14 -13
- package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
- package/data/persistence/documents/internal/ensureDocumentsSchema.js +10 -12
- package/data/persistence/documents/internal/ensureDocumentsSchema.js.map +1 -1
- package/data/persistence/verifiedCitations/citationScope.js +121 -0
- package/data/persistence/verifiedCitations/citationScope.js.map +1 -0
- package/data/persistence/verifiedCitations/kekLevelCitations.d.ts +33 -0
- package/data/persistence/verifiedCitations/kekLevelCitations.js +44 -0
- package/data/persistence/verifiedCitations/kekLevelCitations.js.map +1 -0
- package/data/persistence/verifiedManifestBundlePersistence.d.ts +17 -12
- package/data/persistence/verifiedManifestBundlePersistence.js +75 -104
- package/data/persistence/verifiedManifestBundlePersistence.js.map +1 -1
- package/data/sqlite/schema/containerSchema.d.ts +1 -0
- package/data/sqlite/schema/containerSchema.js +8 -1
- package/data/sqlite/schema/containerSchema.js.map +1 -1
- package/data/sqlite/schema/documentAttachmentSchema.d.ts +0 -2
- package/data/sqlite/schema/documentAttachmentSchema.js +0 -4
- package/data/sqlite/schema/documentAttachmentSchema.js.map +1 -1
- package/data/sqlite/schema/index.d.ts +2 -0
- package/data/sqlite/schema/index.js +10 -1
- package/data/sqlite/schema/index.js.map +1 -1
- package/data/sqlite/schema/principalPolicySchema.js +0 -1
- package/data/sqlite/schema/principalPolicySchema.js.map +1 -1
- package/data/sqlite/schema/verifiedManifestBundleSchema.d.ts +7 -2
- package/data/sqlite/schema/verifiedManifestBundleSchema.js +11 -4
- package/data/sqlite/schema/verifiedManifestBundleSchema.js.map +1 -1
- package/data/sqlite/sqlExec.d.ts +12 -0
- package/data/sqlite/sqlExec.js +47 -3
- package/data/sqlite/sqlExec.js.map +1 -1
- package/data/sqlite/sqlTableSchema.js +40 -15
- package/data/sqlite/sqlTableSchema.js.map +1 -1
- package/data/sqlite/sqlitePersistenceRuntime.js +18 -10
- package/data/sqlite/sqlitePersistenceRuntime.js.map +1 -1
- package/internal/api-client/ApiClient.d.ts +3 -2
- package/internal/api-client/ApiClient.js +3 -2
- package/internal/api-client/ApiClient.js.map +1 -1
- package/internal/api-client/index.d.ts +1 -1
- package/internal/api-client/index.js.map +1 -1
- package/internal/api-client/types.d.ts +7 -0
- package/internal/api-client/types.js.map +1 -1
- package/internal/api-client/verifiedManifestCitations.d.ts +26 -9
- package/internal/api-client/verifiedManifestCitations.js +44 -25
- package/internal/api-client/verifiedManifestCitations.js.map +1 -1
- package/internal/api-client/writerProjectionRequests.js +21 -10
- package/internal/api-client/writerProjectionRequests.js.map +1 -1
- package/internal/crypto/index.d.ts +1 -1
- package/internal/crypto/index.js +1 -1
- package/internal/crypto/index.js.map +1 -1
- package/internal/crypto/keying/canonicalJsonGuard.d.ts +7 -0
- package/internal/crypto/keying/canonicalJsonGuard.js +20 -0
- package/internal/crypto/keying/canonicalJsonGuard.js.map +1 -1
- package/internal/crypto/keying/index.d.ts +1 -1
- package/internal/crypto/keying/index.js +1 -1
- package/internal/crypto/keying/index.js.map +1 -1
- package/internal/validators/knownManifestsRefinements.js +1 -1
- package/internal/validators/knownManifestsRefinements.js.map +1 -1
- package/internal/validators/operation/writerProjections.d.ts +42 -18
- package/internal/validators/response/container.d.ts +137 -16
- package/internal/validators/response/container.js +23 -2
- package/internal/validators/response/container.js.map +1 -1
- package/internal/validators/response/documentMutation.d.ts +20 -8
- package/internal/validators/response/index.d.ts +2 -2
- package/internal/validators/response/index.js +2 -2
- package/internal/validators/response/index.js.map +1 -1
- package/internal/validators/response/writerProjectionWire.d.ts +19 -8
- package/internal/validators/response/writerProjectionWire.js +78 -20
- package/internal/validators/response/writerProjectionWire.js.map +1 -1
- package/internal/validators/util/index.d.ts +1 -1
- package/internal/validators/util/index.js +1 -1
- package/internal/validators/util/index.js.map +1 -1
- package/internal/validators/util/knownManifestWire.d.ts +13 -3
- package/internal/validators/util/knownManifestWire.js +37 -10
- package/internal/validators/util/knownManifestWire.js.map +1 -1
- package/package.json +1 -1
- package/workflows/container-contents/remoteHydration/destination/destinationPrefetch.js +10 -5
- package/workflows/container-contents/remoteHydration/destination/destinationPrefetch.js.map +1 -1
- package/workflows/container-contents/remoteHydration/remoteContainers/remoteContainerPageHydration.js +4 -6
- package/workflows/container-contents/remoteHydration/remoteContainers/remoteContainerPageHydration.js.map +1 -1
- package/workflows/container-contents/remoteHydration/types.d.ts +2 -2
- package/workflows/container-contents/remoteHydration/types.js.map +1 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { MAX_CONTAINER_PATH_LENGTH } from "../../../internal/validators/util/index.js";
|
|
2
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
3
|
+
import { documentContainerProjection } from "../../sqlite/schema/index.js";
|
|
4
|
+
import { containers } from "../../sqlite/schema/containerSchema.js";
|
|
5
|
+
import { documentDiscoveryHeads, pendingDocumentDiscoveries, } from "../../sqlite/schema/documentDiscoveryEvidenceSchema.js";
|
|
6
|
+
import { verifiedAccessManifestBundles as bundles } from "../../sqlite/schema/verifiedManifestBundleSchema.js";
|
|
7
|
+
/** The requested object first, then each container whose lineage it cites. */
|
|
8
|
+
async function containerLineage(db, containerId, prefix = []) {
|
|
9
|
+
const lineage = [...prefix, containerId];
|
|
10
|
+
while (lineage.length < MAX_CONTAINER_PATH_LENGTH) {
|
|
11
|
+
const [row] = await db
|
|
12
|
+
.select({ parentId: containers.parentId })
|
|
13
|
+
.from(containers)
|
|
14
|
+
.where(eq(containers.id, lineage.at(-1) ?? ""))
|
|
15
|
+
.limit(1);
|
|
16
|
+
const parentId = row?.parentId;
|
|
17
|
+
if (!parentId || lineage.includes(parentId))
|
|
18
|
+
break;
|
|
19
|
+
lineage.push(parentId);
|
|
20
|
+
}
|
|
21
|
+
return lineage;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A container the device has not stored yet has no local parent, so its
|
|
25
|
+
* listing names one: the lineage continues through the listed parent's
|
|
26
|
+
* stored lineage. A wrong parent only changes which held hashes are cited.
|
|
27
|
+
*/
|
|
28
|
+
async function containerTargetLineage(db, target) {
|
|
29
|
+
const parentId = target.listing?.parentId;
|
|
30
|
+
if (!parentId || parentId === target.id)
|
|
31
|
+
return containerLineage(db, target.id);
|
|
32
|
+
const [stored] = await db
|
|
33
|
+
.select({ id: containers.id })
|
|
34
|
+
.from(containers)
|
|
35
|
+
.where(eq(containers.id, target.id))
|
|
36
|
+
.limit(1);
|
|
37
|
+
return stored
|
|
38
|
+
? containerLineage(db, target.id)
|
|
39
|
+
: containerLineage(db, parentId, [target.id]);
|
|
40
|
+
}
|
|
41
|
+
export async function citedObjects(db, target) {
|
|
42
|
+
if (target.kind === "container")
|
|
43
|
+
return (await containerTargetLineage(db, target)).map((id) => ({
|
|
44
|
+
kind: "container",
|
|
45
|
+
id,
|
|
46
|
+
}));
|
|
47
|
+
// Linked containers, containers listing the document before its signed
|
|
48
|
+
// placement is checked, and a container whose metadata document it is.
|
|
49
|
+
const placements = [
|
|
50
|
+
...(await db
|
|
51
|
+
.select({ containerId: documentContainerProjection.containerId })
|
|
52
|
+
.from(documentContainerProjection)
|
|
53
|
+
.where(eq(documentContainerProjection.documentId, target.id))),
|
|
54
|
+
...(await db
|
|
55
|
+
.select({ containerId: pendingDocumentDiscoveries.containerId })
|
|
56
|
+
.from(pendingDocumentDiscoveries)
|
|
57
|
+
.where(eq(pendingDocumentDiscoveries.documentId, target.id))),
|
|
58
|
+
...(await db
|
|
59
|
+
.select({ containerId: containers.id })
|
|
60
|
+
.from(containers)
|
|
61
|
+
.where(eq(containers.metadataDocumentId, target.id))),
|
|
62
|
+
];
|
|
63
|
+
const containerIds = new Set();
|
|
64
|
+
for (const { containerId } of placements)
|
|
65
|
+
if (containerId)
|
|
66
|
+
for (const id of await containerLineage(db, containerId))
|
|
67
|
+
containerIds.add(id);
|
|
68
|
+
return [
|
|
69
|
+
{ kind: "document", id: target.id },
|
|
70
|
+
...[...containerIds].map((id) => ({ kind: "container", id })),
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The one organization every local source places the read in. Verified
|
|
75
|
+
* ownership (stored verified bundles of the target and of the containers on
|
|
76
|
+
* its walk, and a document's verified discovery head) and local placement
|
|
77
|
+
* (listed container rows reached through links, pending listings, metadata,
|
|
78
|
+
* or parents) must all agree: any disagreement, or no source at all, yields
|
|
79
|
+
* null, so the read is sent with no hints rather than another
|
|
80
|
+
* organization's.
|
|
81
|
+
*/
|
|
82
|
+
export async function targetOrganization(db, target, objects) {
|
|
83
|
+
const containerIds = objects
|
|
84
|
+
.filter((object) => object.kind === "container")
|
|
85
|
+
.map((object) => object.id);
|
|
86
|
+
const listed = containerIds.length === 0
|
|
87
|
+
? []
|
|
88
|
+
: await db
|
|
89
|
+
.select({ organizationId: containers.organizationId })
|
|
90
|
+
.from(containers)
|
|
91
|
+
.where(inArray(containers.id, containerIds));
|
|
92
|
+
const verifiedContainers = containerIds.length === 0
|
|
93
|
+
? []
|
|
94
|
+
: await db
|
|
95
|
+
.select({ organizationId: bundles.organizationId })
|
|
96
|
+
.from(bundles)
|
|
97
|
+
.where(and(eq(bundles.objectKind, "container"), inArray(bundles.objectId, containerIds)));
|
|
98
|
+
const verifiedTarget = await db
|
|
99
|
+
.select({ organizationId: bundles.organizationId })
|
|
100
|
+
.from(bundles)
|
|
101
|
+
.where(and(eq(bundles.objectKind, target.kind), eq(bundles.objectId, target.id)));
|
|
102
|
+
const discovered = target.kind === "document"
|
|
103
|
+
? await db
|
|
104
|
+
.select({ organizationId: documentDiscoveryHeads.organizationId })
|
|
105
|
+
.from(documentDiscoveryHeads)
|
|
106
|
+
.where(eq(documentDiscoveryHeads.documentId, target.id))
|
|
107
|
+
: [];
|
|
108
|
+
// The listing that placed an unstored container must agree as well.
|
|
109
|
+
const listing = target.listing ? [target.listing] : [];
|
|
110
|
+
const sources = [
|
|
111
|
+
listed,
|
|
112
|
+
verifiedContainers,
|
|
113
|
+
verifiedTarget,
|
|
114
|
+
discovered,
|
|
115
|
+
listing,
|
|
116
|
+
];
|
|
117
|
+
const organizations = new Set(sources.flat().map((row) => row.organizationId));
|
|
118
|
+
const [organizationId] = organizations;
|
|
119
|
+
return organizations.size === 1 && organizationId ? organizationId : null;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=citationScope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"citationScope.js","sourceRoot":"","sources":["citationScope.ts"],"names":[],"mappings":"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","sourcesContent":["import type { WriterProjectionTarget } from \"@tearleads/api-client\";\nimport { MAX_CONTAINER_PATH_LENGTH } from \"@tearleads/validators/util\";\nimport { and, eq, inArray } from \"drizzle-orm\";\nimport { documentContainerProjection } from \"../../sqlite/schema\";\nimport { containers } from \"../../sqlite/schema/containerSchema\";\nimport {\n documentDiscoveryHeads,\n pendingDocumentDiscoveries,\n} from \"../../sqlite/schema/documentDiscoveryEvidenceSchema\";\nimport { verifiedAccessManifestBundles as bundles } from \"../../sqlite/schema/verifiedManifestBundleSchema\";\nimport type { ClientSQLiteDatabase } from \"../../sqlite/sqlitePersistenceRuntime\";\n\n/** An object whose verified bytes a projection read may cite. */\nexport interface CitedObject {\n readonly kind: string;\n readonly id: string;\n}\n\n/** The requested object first, then each container whose lineage it cites. */\nasync function containerLineage(\n db: ClientSQLiteDatabase,\n containerId: string,\n prefix: readonly string[] = [],\n): Promise<string[]> {\n const lineage = [...prefix, 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\n/**\n * A container the device has not stored yet has no local parent, so its\n * listing names one: the lineage continues through the listed parent's\n * stored lineage. A wrong parent only changes which held hashes are cited.\n */\nasync function containerTargetLineage(\n db: ClientSQLiteDatabase,\n target: WriterProjectionTarget,\n): Promise<string[]> {\n const parentId = target.listing?.parentId;\n if (!parentId || parentId === target.id)\n return containerLineage(db, target.id);\n const [stored] = await db\n .select({ id: containers.id })\n .from(containers)\n .where(eq(containers.id, target.id))\n .limit(1);\n return stored\n ? containerLineage(db, target.id)\n : containerLineage(db, parentId, [target.id]);\n}\n\nexport async function citedObjects(\n db: ClientSQLiteDatabase,\n target: WriterProjectionTarget,\n): Promise<{ readonly kind: string; readonly id: string }[]> {\n if (target.kind === \"container\")\n return (await containerTargetLineage(db, target)).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 */\nexport async 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 // The listing that placed an unstored container must agree as well.\n const listing = target.listing ? [target.listing] : [];\n const sources = [\n listed,\n verifiedContainers,\n verifiedTarget,\n discovered,\n listing,\n ];\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"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ContainerKekWireLevel } from "../../../internal/validators/response/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Typed rows in the verified-bundle cache that hold one container KEK level
|
|
4
|
+
* each. `manifestHash` (the cache key) and `bundleDigest` are the level's
|
|
5
|
+
* citation digest, the SHA-256 of `bundleJson`, its canonical wire bytes;
|
|
6
|
+
* `objectId` and `epoch` are the level's container and key epoch.
|
|
7
|
+
*/
|
|
8
|
+
export declare const KEK_LEVEL_OBJECT_KIND = "container-kek-level";
|
|
9
|
+
/** A KEK level a projection verification accepted, with its organization. */
|
|
10
|
+
export interface VerifiedKekLevel {
|
|
11
|
+
readonly kekLevel: ContainerKekWireLevel;
|
|
12
|
+
readonly organizationId: string;
|
|
13
|
+
}
|
|
14
|
+
export interface KekLevelRow {
|
|
15
|
+
readonly manifestHash: string;
|
|
16
|
+
readonly organizationId: string;
|
|
17
|
+
readonly objectKind: string;
|
|
18
|
+
readonly objectId: string;
|
|
19
|
+
readonly epoch: number;
|
|
20
|
+
readonly bundleJson: string;
|
|
21
|
+
readonly bundleDigest: string;
|
|
22
|
+
}
|
|
23
|
+
/** Null when the level has no canonical form; such a level is not cited. */
|
|
24
|
+
export declare function kekLevelRow({ kekLevel, organizationId, }: VerifiedKekLevel): Promise<KekLevelRow | null>;
|
|
25
|
+
/**
|
|
26
|
+
* The stored level, or null for a damaged row: its bytes must parse as a
|
|
27
|
+
* full KEK level whose recomputed digest is the row's key.
|
|
28
|
+
*/
|
|
29
|
+
export declare function readStoredKekLevel(row: {
|
|
30
|
+
readonly bundleDigest: string;
|
|
31
|
+
readonly bundleJson: string;
|
|
32
|
+
readonly manifestHash: string;
|
|
33
|
+
}): Promise<ContainerKekWireLevel | null>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { computeContainerKekLevelDigest, readKeyingCanonicalJson, serializeKeyingCanonicalJson, } from "../../../internal/crypto/index.js";
|
|
2
|
+
import { isContainerKekWireLevel, } from "../../../internal/validators/response/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Typed rows in the verified-bundle cache that hold one container KEK level
|
|
5
|
+
* each. `manifestHash` (the cache key) and `bundleDigest` are the level's
|
|
6
|
+
* citation digest, the SHA-256 of `bundleJson`, its canonical wire bytes;
|
|
7
|
+
* `objectId` and `epoch` are the level's container and key epoch.
|
|
8
|
+
*/
|
|
9
|
+
export const KEK_LEVEL_OBJECT_KIND = "container-kek-level";
|
|
10
|
+
/** Null when the level has no canonical form; such a level is not cited. */
|
|
11
|
+
export async function kekLevelRow({ kekLevel, organizationId, }) {
|
|
12
|
+
const digest = await computeContainerKekLevelDigest(kekLevel);
|
|
13
|
+
if (digest === null)
|
|
14
|
+
return null;
|
|
15
|
+
return {
|
|
16
|
+
manifestHash: digest,
|
|
17
|
+
organizationId,
|
|
18
|
+
objectKind: KEK_LEVEL_OBJECT_KIND,
|
|
19
|
+
objectId: kekLevel.containerId,
|
|
20
|
+
epoch: kekLevel.containerKeyEpoch,
|
|
21
|
+
// Canonical: the digest above is the SHA-256 of exactly these bytes.
|
|
22
|
+
bundleJson: serializeKeyingCanonicalJson(readKeyingCanonicalJson(kekLevel, "Verified KEK level")),
|
|
23
|
+
bundleDigest: digest,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The stored level, or null for a damaged row: its bytes must parse as a
|
|
28
|
+
* full KEK level whose recomputed digest is the row's key.
|
|
29
|
+
*/
|
|
30
|
+
export async function readStoredKekLevel(row) {
|
|
31
|
+
if (row.bundleDigest !== row.manifestHash)
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
const level = JSON.parse(row.bundleJson);
|
|
35
|
+
return isContainerKekWireLevel(level) &&
|
|
36
|
+
(await computeContainerKekLevelDigest(level)) === row.manifestHash
|
|
37
|
+
? level
|
|
38
|
+
: null;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=kekLevelCitations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kekLevelCitations.js","sourceRoot":"","sources":["kekLevelCitations.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEL,uBAAuB,GACxB,MAAM,gCAAgC,CAAC;AAExC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;AAkB3D,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAChC,QAAQ,EACR,cAAc,GACG;IACjB,MAAM,MAAM,GAAG,MAAM,8BAA8B,CAAC,QAAQ,CAAC,CAAC;IAC9D,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,OAAO;QACL,YAAY,EAAE,MAAM;QACpB,cAAc;QACd,UAAU,EAAE,qBAAqB;QACjC,QAAQ,EAAE,QAAQ,CAAC,WAAW;QAC9B,KAAK,EAAE,QAAQ,CAAC,iBAAiB;QACjC,qEAAqE;QACrE,UAAU,EAAE,4BAA4B,CACtC,uBAAuB,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CACxD;QACD,YAAY,EAAE,MAAM;KACrB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAIxC;IACC,IAAI,GAAG,CAAC,YAAY,KAAK,GAAG,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,CAAC;QACH,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAClD,OAAO,uBAAuB,CAAC,KAAK,CAAC;YACnC,CAAC,MAAM,8BAA8B,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,YAAY;YAClE,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import {\n computeContainerKekLevelDigest,\n readKeyingCanonicalJson,\n serializeKeyingCanonicalJson,\n} from \"@tearleads/crypto\";\nimport {\n type ContainerKekWireLevel,\n isContainerKekWireLevel,\n} from \"@tearleads/validators/response\";\n\n/**\n * Typed rows in the verified-bundle cache that hold one container KEK level\n * each. `manifestHash` (the cache key) and `bundleDigest` are the level's\n * citation digest, the SHA-256 of `bundleJson`, its canonical wire bytes;\n * `objectId` and `epoch` are the level's container and key epoch.\n */\nexport const KEK_LEVEL_OBJECT_KIND = \"container-kek-level\";\n\n/** A KEK level a projection verification accepted, with its organization. */\nexport interface VerifiedKekLevel {\n readonly kekLevel: ContainerKekWireLevel;\n readonly organizationId: string;\n}\n\nexport interface KekLevelRow {\n readonly manifestHash: string;\n readonly organizationId: string;\n readonly objectKind: string;\n readonly objectId: string;\n readonly epoch: number;\n readonly bundleJson: string;\n readonly bundleDigest: string;\n}\n\n/** Null when the level has no canonical form; such a level is not cited. */\nexport async function kekLevelRow({\n kekLevel,\n organizationId,\n}: VerifiedKekLevel): Promise<KekLevelRow | null> {\n const digest = await computeContainerKekLevelDigest(kekLevel);\n if (digest === null) return null;\n return {\n manifestHash: digest,\n organizationId,\n objectKind: KEK_LEVEL_OBJECT_KIND,\n objectId: kekLevel.containerId,\n epoch: kekLevel.containerKeyEpoch,\n // Canonical: the digest above is the SHA-256 of exactly these bytes.\n bundleJson: serializeKeyingCanonicalJson(\n readKeyingCanonicalJson(kekLevel, \"Verified KEK level\"),\n ),\n bundleDigest: digest,\n };\n}\n\n/**\n * The stored level, or null for a damaged row: its bytes must parse as a\n * full KEK level whose recomputed digest is the row's key.\n */\nexport async function readStoredKekLevel(row: {\n readonly bundleDigest: string;\n readonly bundleJson: string;\n readonly manifestHash: string;\n}): Promise<ContainerKekWireLevel | null> {\n if (row.bundleDigest !== row.manifestHash) return null;\n try {\n const level: unknown = JSON.parse(row.bundleJson);\n return isContainerKekWireLevel(level) &&\n (await computeContainerKekLevelDigest(level)) === row.manifestHash\n ? level\n : null;\n } catch {\n return null;\n }\n}\n"]}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import type { VerifiedManifestCitations, WriterProjectionTarget } from "../../internal/api-client/index.js";
|
|
1
|
+
import type { HeldProjectionCitation, VerifiedManifestCitations, WriterProjectionTarget } from "../../internal/api-client/index.js";
|
|
2
2
|
import { type AccessManifestBundleWireResponse } from "../../internal/validators/response/index.js";
|
|
3
3
|
import { verifiedAccessManifestBundles as bundles } from "../sqlite/schema/verifiedManifestBundleSchema.js";
|
|
4
4
|
import { type ClientSQLiteTransactionScope } from "../sqlite/sqlitePersistenceRuntime.js";
|
|
5
5
|
import { type ExecSql } from "../sqlite/sqlSchema.js";
|
|
6
|
+
import { type VerifiedKekLevel } from "./verifiedCitations/kekLevelCitations.js";
|
|
6
7
|
/**
|
|
7
8
|
* Bounds local bytes (a bundle is ~10–20 KB). Evicting the least recently
|
|
8
9
|
* verified rows only makes a later read carry those bundles again.
|
|
9
10
|
*/
|
|
10
11
|
export declare const MAX_VERIFIED_MANIFEST_BUNDLES = 1024;
|
|
11
|
-
|
|
12
|
+
interface VerifiedBundle {
|
|
12
13
|
readonly bundle: AccessManifestBundleWireResponse;
|
|
13
14
|
readonly checkpoint: {
|
|
14
15
|
readonly objectKind: string;
|
|
@@ -17,12 +18,14 @@ export interface VerifiedManifestBundle {
|
|
|
17
18
|
readonly epoch: number;
|
|
18
19
|
};
|
|
19
20
|
}
|
|
21
|
+
/** Bytes a projection verification accepted: a bundle or a KEK level. */
|
|
22
|
+
export type VerifiedManifestBundle = VerifiedBundle | VerifiedKekLevel;
|
|
20
23
|
/** A verified bundle's exact JSON and that JSON's digest. */
|
|
21
24
|
export type VerifiedManifestBundleRow = Omit<typeof bundles.$inferInsert, "verifiedAt">;
|
|
22
25
|
/**
|
|
23
|
-
* Prepares rows for bundles that just passed projection
|
|
24
|
-
* digests are computed here, before the checkpoint
|
|
25
|
-
* them begins.
|
|
26
|
+
* Prepares rows for bundles and KEK levels that just passed projection
|
|
27
|
+
* verification. The digests are computed here, before the checkpoint
|
|
28
|
+
* transaction that writes them begins.
|
|
26
29
|
*/
|
|
27
30
|
export declare function verifiedManifestBundleRows(entries: readonly VerifiedManifestBundle[]): Promise<VerifiedManifestBundleRow[]>;
|
|
28
31
|
/**
|
|
@@ -33,15 +36,17 @@ export declare function verifiedManifestBundleRows(entries: readonly VerifiedMan
|
|
|
33
36
|
*/
|
|
34
37
|
export declare function writeVerifiedManifestBundlesInTransaction(tx: ClientSQLiteTransactionScope, rows: readonly VerifiedManifestBundleRow[], verifiedAt: string): Promise<void>;
|
|
35
38
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* of its organization, since a
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* Damaged rows are
|
|
39
|
+
* The latest verified KEK level of each container the target's projection
|
|
40
|
+
* walks; then verified bundles of those objects, newest epochs first; then
|
|
41
|
+
* the most recently verified bundles of its organization, since a
|
|
42
|
+
* just-verified parent is the likeliest part of a new child's projection.
|
|
43
|
+
* Citations never leave the target's organization: an object whose
|
|
44
|
+
* organization the device cannot place cites nothing. Damaged rows are
|
|
45
|
+
* dropped.
|
|
42
46
|
*/
|
|
43
|
-
export declare function loadVerifiedManifestCitations(execSql: ExecSql, target: WriterProjectionTarget, limit?: number): Promise<
|
|
47
|
+
export declare function loadVerifiedManifestCitations(execSql: ExecSql, target: WriterProjectionTarget, limit?: number): Promise<HeldProjectionCitation[]>;
|
|
44
48
|
/** Cites from whichever database the signed-in identity currently uses. */
|
|
45
49
|
export declare function createVerifiedManifestCitations(resolveExecSql: () => ExecSql | null): VerifiedManifestCitations;
|
|
46
50
|
/** Drops an organization's verified bundles when a remote reset forgets it. */
|
|
47
51
|
export declare function deleteVerifiedManifestBundlesForOrganization(tx: ClientSQLiteTransactionScope, organizationId: string): Promise<void>;
|
|
52
|
+
export {};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { toFingerprint } from "../../internal/crypto/index.js";
|
|
2
2
|
import { isAccessManifestBundleWireResponse, } from "../../internal/validators/response/index.js";
|
|
3
|
-
import { KNOWN_MANIFEST_HINT_LIMIT
|
|
4
|
-
import { and, asc, desc, eq, inArray, sql } from "drizzle-orm";
|
|
5
|
-
import { containerTables,
|
|
6
|
-
import { containers } from "../sqlite/schema/containerSchema.js";
|
|
7
|
-
import { documentDiscoveryHeads, pendingDocumentDiscoveries, } from "../sqlite/schema/documentDiscoveryEvidenceSchema.js";
|
|
3
|
+
import { KNOWN_MANIFEST_HINT_LIMIT } from "../../internal/validators/util/index.js";
|
|
4
|
+
import { and, asc, desc, eq, inArray, ne, notInArray, sql } from "drizzle-orm";
|
|
5
|
+
import { containerTables, documentContainerProjectionTables, } from "../sqlite/schema/index.js";
|
|
8
6
|
import { verifiedAccessManifestBundles as bundles, verifiedManifestBundleTables, } from "../sqlite/schema/verifiedManifestBundleSchema.js";
|
|
9
7
|
import { sqliteInsertBatchSize } from "../sqlite/sqliteBindBudget.js";
|
|
10
8
|
import { getClientSQLitePersistenceRuntime, } from "../sqlite/sqlitePersistenceRuntime.js";
|
|
11
9
|
import { excludedValue } from "../sqlite/sqliteUpsert.js";
|
|
12
10
|
import { ensureSqlTables } from "../sqlite/sqlSchema.js";
|
|
11
|
+
import { citedObjects, targetOrganization, } from "./verifiedCitations/citationScope.js";
|
|
12
|
+
import { KEK_LEVEL_OBJECT_KIND, kekLevelRow, readStoredKekLevel, } from "./verifiedCitations/kekLevelCitations.js";
|
|
13
13
|
/**
|
|
14
14
|
* Bounds local bytes (a bundle is ~10–20 KB). Evicting the least recently
|
|
15
15
|
* verified rows only makes a later read carry those bundles again.
|
|
@@ -43,11 +43,29 @@ async function pruneInTransaction(tx) {
|
|
|
43
43
|
await tx.delete(bundles).where(inArray(bundles.manifestHash, batch)).run();
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Prepares rows for bundles that just passed projection
|
|
47
|
-
* digests are computed here, before the checkpoint
|
|
48
|
-
* them begins.
|
|
46
|
+
* Prepares rows for bundles and KEK levels that just passed projection
|
|
47
|
+
* verification. The digests are computed here, before the checkpoint
|
|
48
|
+
* transaction that writes them begins.
|
|
49
49
|
*/
|
|
50
|
-
export function verifiedManifestBundleRows(entries) {
|
|
50
|
+
export async function verifiedManifestBundleRows(entries) {
|
|
51
|
+
const bundleEntries = [];
|
|
52
|
+
const levelEntries = [];
|
|
53
|
+
for (const entry of entries)
|
|
54
|
+
if ("kekLevel" in entry)
|
|
55
|
+
levelEntries.push(entry);
|
|
56
|
+
else
|
|
57
|
+
bundleEntries.push(entry);
|
|
58
|
+
// One level per container: its newest epoch, the last one seen on a tie.
|
|
59
|
+
const levelRows = new Map();
|
|
60
|
+
for (const level of levelEntries) {
|
|
61
|
+
const row = await kekLevelRow(level);
|
|
62
|
+
const held = row && levelRows.get(row.objectId);
|
|
63
|
+
if (row && (!held || held.epoch <= row.epoch))
|
|
64
|
+
levelRows.set(row.objectId, row);
|
|
65
|
+
}
|
|
66
|
+
return [...(await bundleRows(bundleEntries)), ...levelRows.values()];
|
|
67
|
+
}
|
|
68
|
+
function bundleRows(entries) {
|
|
51
69
|
const unique = new Map(entries.map((entry) => [entry.bundle.manifestHash, entry]));
|
|
52
70
|
return Promise.all([...unique.values()].map(async ({ bundle, checkpoint }) => {
|
|
53
71
|
const bundleJson = JSON.stringify(bundle);
|
|
@@ -101,8 +119,21 @@ export async function writeVerifiedManifestBundlesInTransaction(tx, rows, verifi
|
|
|
101
119
|
},
|
|
102
120
|
})
|
|
103
121
|
.run();
|
|
122
|
+
await dropSupersededKekLevels(tx, rows);
|
|
104
123
|
await pruneInTransaction(tx);
|
|
105
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* A container keeps only its latest verified level: the API serves its
|
|
127
|
+
* current level, so an older one can never be cited usefully again.
|
|
128
|
+
*/
|
|
129
|
+
async function dropSupersededKekLevels(tx, rows) {
|
|
130
|
+
const levels = rows.filter((row) => row.objectKind === KEK_LEVEL_OBJECT_KIND);
|
|
131
|
+
for (const batch of batches(levels))
|
|
132
|
+
await tx
|
|
133
|
+
.delete(bundles)
|
|
134
|
+
.where(and(eq(bundles.objectKind, KEK_LEVEL_OBJECT_KIND), inArray(bundles.objectId, batch.map((row) => row.objectId)), notInArray(bundles.manifestHash, batch.map((row) => row.manifestHash))))
|
|
135
|
+
.run();
|
|
136
|
+
}
|
|
106
137
|
async function storedDigests(tx, manifestHashes) {
|
|
107
138
|
const digests = new Map();
|
|
108
139
|
for (const batch of batches(manifestHashes)) {
|
|
@@ -118,94 +149,6 @@ async function storedDigests(tx, manifestHashes) {
|
|
|
118
149
|
}
|
|
119
150
|
return digests;
|
|
120
151
|
}
|
|
121
|
-
/** The requested object first, then each container whose lineage it cites. */
|
|
122
|
-
async function containerLineage(db, containerId) {
|
|
123
|
-
const lineage = [containerId];
|
|
124
|
-
while (lineage.length < MAX_CONTAINER_PATH_LENGTH) {
|
|
125
|
-
const [row] = await db
|
|
126
|
-
.select({ parentId: containers.parentId })
|
|
127
|
-
.from(containers)
|
|
128
|
-
.where(eq(containers.id, lineage.at(-1) ?? ""))
|
|
129
|
-
.limit(1);
|
|
130
|
-
const parentId = row?.parentId;
|
|
131
|
-
if (!parentId || lineage.includes(parentId))
|
|
132
|
-
break;
|
|
133
|
-
lineage.push(parentId);
|
|
134
|
-
}
|
|
135
|
-
return lineage;
|
|
136
|
-
}
|
|
137
|
-
async function citedObjects(db, target) {
|
|
138
|
-
if (target.kind === "container")
|
|
139
|
-
return (await containerLineage(db, target.id)).map((id) => ({
|
|
140
|
-
kind: "container",
|
|
141
|
-
id,
|
|
142
|
-
}));
|
|
143
|
-
// Linked containers, containers listing the document before its signed
|
|
144
|
-
// placement is checked, and a container whose metadata document it is.
|
|
145
|
-
const placements = [
|
|
146
|
-
...(await db
|
|
147
|
-
.select({ containerId: documentContainerProjection.containerId })
|
|
148
|
-
.from(documentContainerProjection)
|
|
149
|
-
.where(eq(documentContainerProjection.documentId, target.id))),
|
|
150
|
-
...(await db
|
|
151
|
-
.select({ containerId: pendingDocumentDiscoveries.containerId })
|
|
152
|
-
.from(pendingDocumentDiscoveries)
|
|
153
|
-
.where(eq(pendingDocumentDiscoveries.documentId, target.id))),
|
|
154
|
-
...(await db
|
|
155
|
-
.select({ containerId: containers.id })
|
|
156
|
-
.from(containers)
|
|
157
|
-
.where(eq(containers.metadataDocumentId, target.id))),
|
|
158
|
-
];
|
|
159
|
-
const containerIds = new Set();
|
|
160
|
-
for (const { containerId } of placements)
|
|
161
|
-
if (containerId)
|
|
162
|
-
for (const id of await containerLineage(db, containerId))
|
|
163
|
-
containerIds.add(id);
|
|
164
|
-
return [
|
|
165
|
-
{ kind: "document", id: target.id },
|
|
166
|
-
...[...containerIds].map((id) => ({ kind: "container", id })),
|
|
167
|
-
];
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* The one organization every local source places the read in. Verified
|
|
171
|
-
* ownership (stored verified bundles of the target and of the containers on
|
|
172
|
-
* its walk, and a document's verified discovery head) and local placement
|
|
173
|
-
* (listed container rows reached through links, pending listings, metadata,
|
|
174
|
-
* or parents) must all agree: any disagreement, or no source at all, yields
|
|
175
|
-
* null, so the read is sent with no hints rather than another
|
|
176
|
-
* organization's.
|
|
177
|
-
*/
|
|
178
|
-
async function targetOrganization(db, target, objects) {
|
|
179
|
-
const containerIds = objects
|
|
180
|
-
.filter((object) => object.kind === "container")
|
|
181
|
-
.map((object) => object.id);
|
|
182
|
-
const listed = containerIds.length === 0
|
|
183
|
-
? []
|
|
184
|
-
: await db
|
|
185
|
-
.select({ organizationId: containers.organizationId })
|
|
186
|
-
.from(containers)
|
|
187
|
-
.where(inArray(containers.id, containerIds));
|
|
188
|
-
const verifiedContainers = containerIds.length === 0
|
|
189
|
-
? []
|
|
190
|
-
: await db
|
|
191
|
-
.select({ organizationId: bundles.organizationId })
|
|
192
|
-
.from(bundles)
|
|
193
|
-
.where(and(eq(bundles.objectKind, "container"), inArray(bundles.objectId, containerIds)));
|
|
194
|
-
const verifiedTarget = await db
|
|
195
|
-
.select({ organizationId: bundles.organizationId })
|
|
196
|
-
.from(bundles)
|
|
197
|
-
.where(and(eq(bundles.objectKind, target.kind), eq(bundles.objectId, target.id)));
|
|
198
|
-
const discovered = target.kind === "document"
|
|
199
|
-
? await db
|
|
200
|
-
.select({ organizationId: documentDiscoveryHeads.organizationId })
|
|
201
|
-
.from(documentDiscoveryHeads)
|
|
202
|
-
.where(eq(documentDiscoveryHeads.documentId, target.id))
|
|
203
|
-
: [];
|
|
204
|
-
const sources = [listed, verifiedContainers, verifiedTarget, discovered];
|
|
205
|
-
const organizations = new Set(sources.flat().map((row) => row.organizationId));
|
|
206
|
-
const [organizationId] = organizations;
|
|
207
|
-
return organizations.size === 1 && organizationId ? organizationId : null;
|
|
208
|
-
}
|
|
209
152
|
const storedColumns = {
|
|
210
153
|
bundleDigest: bundles.bundleDigest,
|
|
211
154
|
bundleJson: bundles.bundleJson,
|
|
@@ -237,17 +180,36 @@ function recentRows(db, organizationId, limit) {
|
|
|
237
180
|
return db
|
|
238
181
|
.select(storedColumns)
|
|
239
182
|
.from(bundles)
|
|
240
|
-
.where(eq(bundles.organizationId, organizationId))
|
|
183
|
+
.where(and(eq(bundles.organizationId, organizationId), ne(bundles.objectKind, KEK_LEVEL_OBJECT_KIND)))
|
|
241
184
|
.orderBy(desc(bundles.verifiedAt), asc(bundles.manifestHash))
|
|
242
185
|
.limit(limit);
|
|
243
186
|
}
|
|
187
|
+
/** The latest verified KEK level of each container, in lineage order. */
|
|
188
|
+
async function lineageKekLevels(db, organizationId, objects) {
|
|
189
|
+
const containerIds = objects
|
|
190
|
+
.filter((object) => object.kind === "container")
|
|
191
|
+
.map((object) => object.id);
|
|
192
|
+
if (containerIds.length === 0)
|
|
193
|
+
return [];
|
|
194
|
+
const rows = await db
|
|
195
|
+
.select({ ...storedColumns, objectId: bundles.objectId })
|
|
196
|
+
.from(bundles)
|
|
197
|
+
.where(and(eq(bundles.organizationId, organizationId), eq(bundles.objectKind, KEK_LEVEL_OBJECT_KIND), inArray(bundles.objectId, containerIds)))
|
|
198
|
+
.orderBy(desc(bundles.epoch), desc(bundles.verifiedAt));
|
|
199
|
+
const latest = new Map();
|
|
200
|
+
for (const row of rows)
|
|
201
|
+
if (!latest.has(row.objectId))
|
|
202
|
+
latest.set(row.objectId, row);
|
|
203
|
+
return containerIds.flatMap((id) => latest.get(id) ?? []);
|
|
204
|
+
}
|
|
244
205
|
/**
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* of its organization, since a
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
* Damaged rows are
|
|
206
|
+
* The latest verified KEK level of each container the target's projection
|
|
207
|
+
* walks; then verified bundles of those objects, newest epochs first; then
|
|
208
|
+
* the most recently verified bundles of its organization, since a
|
|
209
|
+
* just-verified parent is the likeliest part of a new child's projection.
|
|
210
|
+
* Citations never leave the target's organization: an object whose
|
|
211
|
+
* organization the device cannot place cites nothing. Damaged rows are
|
|
212
|
+
* dropped.
|
|
251
213
|
*/
|
|
252
214
|
export async function loadVerifiedManifestCitations(execSql, target, limit = KNOWN_MANIFEST_HINT_LIMIT) {
|
|
253
215
|
await ensureSqlTables(execSql, [
|
|
@@ -273,6 +235,15 @@ export async function loadVerifiedManifestCitations(execSql, target, limit = KNO
|
|
|
273
235
|
const organizationId = await targetOrganization(db, target, objects);
|
|
274
236
|
if (organizationId === null)
|
|
275
237
|
return [];
|
|
238
|
+
for (const row of await lineageKekLevels(db, organizationId, objects)) {
|
|
239
|
+
if (cited.size >= limit)
|
|
240
|
+
break;
|
|
241
|
+
const level = await readStoredKekLevel(row);
|
|
242
|
+
if (level)
|
|
243
|
+
cited.set(row.manifestHash, level);
|
|
244
|
+
else
|
|
245
|
+
damaged.push(row.manifestHash);
|
|
246
|
+
}
|
|
276
247
|
for (const object of objects)
|
|
277
248
|
if (cited.size < limit)
|
|
278
249
|
await collect(await objectRows(db, organizationId, object, limit - cited.size));
|