@tearleads/client-sdk 0.1.100 → 0.1.102
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/client/containerContents/documentDiscovery.js +2 -2
- package/client/containerContents/documentDiscovery.js.map +1 -1
- package/data/containers/shared/mutationFailures.js.map +1 -1
- package/data/containers/shared/types.d.ts +3 -0
- package/data/containers/shared/types.js.map +1 -1
- package/data/persistence/container-contents/containerAccessDiscovery.js +28 -0
- package/data/persistence/container-contents/containerAccessDiscovery.js.map +1 -0
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js +22 -17
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
- package/data/persistence/documents/documentDiscoveryEvidencePersistence.d.ts +1 -0
- package/data/persistence/documents/documentDiscoveryEvidencePersistence.js +1 -0
- package/data/persistence/documents/documentDiscoveryEvidencePersistence.js.map +1 -1
- package/internal/api-client/ApiClient.d.ts +2 -0
- package/internal/api-client/ApiClient.js +13 -11
- package/internal/api-client/ApiClient.js.map +1 -1
- package/package.json +1 -1
- package/workflows/container-contents/documentDiscovery/documentDiscoveryApply.js +6 -3
- package/workflows/container-contents/documentDiscovery/documentDiscoveryApply.js.map +1 -1
- package/workflows/container-contents/documentDiscovery/documentDiscoveryTypes.d.ts +4 -3
- package/workflows/container-contents/documentDiscovery/documentDiscoveryTypes.js.map +1 -1
- package/workflows/container-contents/documentDiscovery/index.js +15 -9
- package/workflows/container-contents/documentDiscovery/index.js.map +1 -1
- package/workflows/container-contents/syncPause/discoveryGuard.js +12 -11
- package/workflows/container-contents/syncPause/discoveryGuard.js.map +1 -1
- package/workflows/containers/child/share/index.d.ts +14 -4
- package/workflows/containers/child/share/index.js +93 -30
- package/workflows/containers/child/share/index.js.map +1 -1
- package/workflows/containers/rematerialization/principalContainerRematerialization.js +5 -19
- package/workflows/containers/rematerialization/principalContainerRematerialization.js.map +1 -1
- package/workflows/containers/rematerialization/principalContainerRematerializationPlanning.js +38 -0
- package/workflows/containers/rematerialization/principalContainerRematerializationPlanning.js.map +1 -1
- package/workflows/organizations/groupShare/sharePrincipalPolicy.d.ts +1 -0
- package/workflows/organizations/groupShare/sharePrincipalPolicy.js.map +1 -1
|
@@ -131,9 +131,10 @@ export interface DiscoverContainerDocumentsOptions extends ContainerDocumentTomb
|
|
|
131
131
|
onFullListing?: ((documentIds: ReadonlyArray<string>) => void) | undefined;
|
|
132
132
|
loadContainerDocumentWatermark: (containerId: string) => Promise<SyncWatermark | null>;
|
|
133
133
|
listContainerDocumentLanes: ContainerDocumentLaneLister;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
/** Recheck stillCurrent under the mutation lock before persisting a listing. */
|
|
135
|
+
replaceDocumentLinksBatch: (inputs: ReadonlyArray<DocumentLinkInput>, stillCurrent: () => Promise<boolean>) => Promise<void>;
|
|
136
|
+
saveContainerDocumentWatermark: (containerId: string, watermark: SyncWatermark, stillCurrent: () => Promise<boolean>) => Promise<void>;
|
|
137
|
+
upsertDiscoveredDocuments: (inputs: ReadonlyArray<DiscoveredDocumentInput>, stillCurrent: () => Promise<boolean>) => Promise<ReadonlyArray<DocumentSummary>>;
|
|
137
138
|
}
|
|
138
139
|
export interface ListedContainerDocuments {
|
|
139
140
|
isFullListing: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentDiscoveryTypes.js","sourceRoot":"","sources":["documentDiscoveryTypes.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n ListContainerDocumentLanesRequest,\n ListContainerParentLanesRequest,\n} from \"@tearleads/validators/request\";\nimport type {\n EffectiveAccessLevel,\n ReferencedPrincipalStateResponse,\n SyncWatermark,\n} from \"@tearleads/validators/response\";\nimport type {\n DiscoveredDocumentInput,\n DocumentSummary,\n} from \"../../../data/documents/documentSummary\";\nimport type { DiscoveredDocumentCandidate } from \"../../../data/persistence/documents/documentDiscoveryEvidencePersistence\";\n\nexport interface ListedContainerDocument {\n createdAt: string;\n currentAccessEpoch: number;\n currentAccessStateHash: string;\n effectiveAccessLevel?: EffectiveAccessLevel | undefined;\n id: string;\n linkedContainerIds: string[];\n referencedPrincipals: ReferencedPrincipalStateResponse[];\n updatedAt: string;\n}\n\nexport interface ListContainerDocumentsResponse {\n hasMore: boolean;\n items: ListedContainerDocument[];\n nextWatermark: SyncWatermark | null;\n tombstones: ReadonlyArray<{\n containerId: string;\n documentId: string;\n updatedAt: string;\n }>;\n}\n\nexport interface ListedContainer {\n id: string;\n}\n\nexport interface ListContainersResponse {\n hasMore: boolean;\n items: ListedContainer[];\n nextWatermark: SyncWatermark | null;\n}\n\nexport interface ListContainerParentLanesResponse {\n results: ReadonlyArray<{\n laneId: string;\n page: ListContainersResponse;\n }>;\n}\n\nexport type ContainerDocumentLaneResult =\n | { readonly laneId: string; readonly page: ListContainerDocumentsResponse }\n | {\n readonly failure: { readonly code?: string | undefined };\n readonly laneId: string;\n };\n\nexport interface ContainerDocumentDiscoveryApi {\n readonly listContainerDocumentLanes: (\n input: ListContainerDocumentLanesRequest,\n options?: { readonly reportErrors?: boolean | undefined },\n ) => Promise<{\n readonly results: ReadonlyArray<ContainerDocumentLaneResult>;\n } | null>;\n readonly listContainerParentLanes: (\n input: ListContainerParentLanesRequest,\n ) => Promise<ListContainerParentLanesResponse | null>;\n}\n\n/**\n * One page per lane, aligned with the requested lanes: `null` for a lane the\n * server rejected, or `null` overall when the request itself failed.\n */\nexport type ContainerDocumentLaneLister = (\n lanes: ReadonlyArray<{\n readonly containerId: string;\n readonly watermark: SyncWatermark | null;\n }>,\n) => Promise<ReadonlyArray<ListContainerDocumentsResponse | null> | null>;\n\nexport interface DocumentLinkInput {\n accessEpoch?: number | undefined;\n containerIds: ReadonlyArray<string>;\n documentId: string;\n}\n\nexport type ContainerDocumentTombstone =\n ListContainerDocumentsResponse[\"tombstones\"][number];\n\nexport interface VerifiedContainerDocumentTombstone\n extends ContainerDocumentTombstone {\n /** The verified head's link-set epoch, not below the local document epoch. */\n readonly accessEpoch: number;\n /** The verified head link set; it omits `containerId`. */\n readonly linkedContainerIds: ReadonlyArray<string>;\n}\n\n/**\n * What the document's signed head says about a listing tombstone.\n * - `verified`: the verified head link set omits the container; apply.\n * - `refuted`: the verified head still links the container; keep visible and retry.\n * - `unverified`: no verified head is available; hold, hide, retry later.\n * - `deferred`: not attempted this run (the per-run head-load cap); hold\n * without counting an attempt, so the retry backoff does not grow.\n */\nexport type ContainerDocumentTombstoneVerdict =\n | {\n readonly kind: \"verified\";\n readonly tombstone: VerifiedContainerDocumentTombstone;\n }\n | {\n readonly kind: \"refuted\";\n /** The verified head link set, which contains the container. */\n readonly linkedContainerIds: ReadonlyArray<string>;\n readonly tombstone: ContainerDocumentTombstone;\n }\n | {\n readonly kind: \"unverified\";\n readonly tombstone: ContainerDocumentTombstone;\n }\n | {\n readonly kind: \"deferred\";\n readonly tombstone: ContainerDocumentTombstone;\n };\n\nexport interface HeldContainerDocumentTombstoneInput\n extends ContainerDocumentTombstone {\n /** Held without a verification attempt; the backoff does not advance. */\n readonly deferred?: boolean | undefined;\n /** A verified head still links it; retry the tombstone while keeping it visible. */\n readonly refuted?: boolean | undefined;\n}\n\nexport type ContainerDocumentTombstoneVerifier = (\n tombstones: ReadonlyArray<ContainerDocumentTombstone>,\n) => Promise<ReadonlyArray<ContainerDocumentTombstoneVerdict>>;\n\nexport type ContainerDocumentPlacement = Pick<\n ContainerDocumentTombstone,\n \"containerId\" | \"documentId\"\n>;\n\nexport interface ContainerDocumentTombstoneHoldStore {\n holdContainerDocumentTombstones: (\n tombstones: ReadonlyArray<HeldContainerDocumentTombstoneInput>,\n ) => Promise<void>;\n /** Held tombstones on these containers that are due for another attempt. */\n listHeldContainerDocumentTombstones: (\n containerIds: ReadonlyArray<string>,\n ) => Promise<ReadonlyArray<ContainerDocumentTombstone>>;\n /** The subset of placements that exist locally (link row or primary). */\n listKnownContainerDocumentPlacements: (\n placements: ReadonlyArray<ContainerDocumentPlacement>,\n ) => Promise<ReadonlyArray<ContainerDocumentPlacement>>;\n refuteContainerDocumentTombstoneHolds: (\n placements: ReadonlyArray<ContainerDocumentPlacement>,\n ) => Promise<void>;\n}\n\nexport interface DiscoverContainerDocumentsOptions\n extends ContainerDocumentTombstoneHoldStore {\n applyContainerDocumentTombstones: (\n tombstones: ReadonlyArray<VerifiedContainerDocumentTombstone>,\n ) => Promise<ReadonlyArray<DocumentSummary>>;\n verifyContainerDocumentTombstones: ContainerDocumentTombstoneVerifier;\n /** Allocate durable local order before fetching a listing. */\n beginDocumentDiscovery: () => Promise<number>;\n /** Persist pending candidates and return only signed placements. */\n verifyDiscoveredDocuments: (\n inputs: ReadonlyArray<DiscoveredDocumentCandidate>,\n containerIds: ReadonlyArray<string>,\n generation: number,\n tombstones?: ReadonlyArray<ContainerDocumentTombstone>,\n ) => Promise<{\n inputs: ReadonlyArray<DiscoveredDocumentInput>;\n /** False once a remote trust reset cancels this listing pass. */\n isCurrent: () => Promise<boolean>;\n /** Acknowledge only after local apply; true means no pending candidates remain. */\n commit: () => Promise<boolean>;\n }>;\n cacheReferencedPrincipalPolicies?: (\n references: ReadonlyArray<ReferencedPrincipalStateResponse>,\n ) => Promise<void>;\n containerId: string;\n /** Completed unwatermarked listing, published only after local apply succeeds. */\n onFullListing?: ((documentIds: ReadonlyArray<string>) => void) | undefined;\n loadContainerDocumentWatermark: (\n containerId: string,\n ) => Promise<SyncWatermark | null>;\n listContainerDocumentLanes: ContainerDocumentLaneLister;\n replaceDocumentLinksBatch: (\n inputs: ReadonlyArray<DocumentLinkInput>,\n ) => Promise<void>;\n saveContainerDocumentWatermark: (\n containerId: string,\n watermark: SyncWatermark,\n ) => Promise<void>;\n upsertDiscoveredDocuments: (\n inputs: ReadonlyArray<DiscoveredDocumentInput>,\n ) => Promise<ReadonlyArray<DocumentSummary>>;\n}\n\nexport interface ListedContainerDocuments {\n isFullListing: boolean;\n items: ListedContainerDocument[];\n nextWatermark: SyncWatermark | null;\n tombstones: ContainerDocumentTombstone[];\n}\n\nexport interface ContainerParentDiscoveryLane {\n parentId: string | null;\n watermark: SyncWatermark | null;\n}\n\nexport interface ListedContainerDocumentsLane {\n containerId: string;\n listedDocuments: ListedContainerDocuments | null;\n}\n\nexport interface DiscoverAllContainerDocumentsOptions\n extends Omit<DiscoverContainerDocumentsOptions, \"containerId\"> {\n containerIds: ReadonlyArray<string>;\n}\n\nexport interface RefreshAllContainerDocumentsOptions\n extends Omit<DiscoverAllContainerDocumentsOptions, \"containerIds\"> {\n listContainerParentLanes: ContainerDocumentDiscoveryApi[\"listContainerParentLanes\"];\n}\n\nexport interface RefreshAllContainerDocumentsFromApiOptions\n extends Omit<\n RefreshAllContainerDocumentsOptions,\n \"listContainerDocumentLanes\" | \"listContainerParentLanes\"\n > {\n readonly apiClient: Pick<\n ContainerDocumentDiscoveryApi,\n \"listContainerDocumentLanes\" | \"listContainerParentLanes\"\n >;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"documentDiscoveryTypes.js","sourceRoot":"","sources":["documentDiscoveryTypes.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n ListContainerDocumentLanesRequest,\n ListContainerParentLanesRequest,\n} from \"@tearleads/validators/request\";\nimport type {\n EffectiveAccessLevel,\n ReferencedPrincipalStateResponse,\n SyncWatermark,\n} from \"@tearleads/validators/response\";\nimport type {\n DiscoveredDocumentInput,\n DocumentSummary,\n} from \"../../../data/documents/documentSummary\";\nimport type { DiscoveredDocumentCandidate } from \"../../../data/persistence/documents/documentDiscoveryEvidencePersistence\";\n\nexport interface ListedContainerDocument {\n createdAt: string;\n currentAccessEpoch: number;\n currentAccessStateHash: string;\n effectiveAccessLevel?: EffectiveAccessLevel | undefined;\n id: string;\n linkedContainerIds: string[];\n referencedPrincipals: ReferencedPrincipalStateResponse[];\n updatedAt: string;\n}\n\nexport interface ListContainerDocumentsResponse {\n hasMore: boolean;\n items: ListedContainerDocument[];\n nextWatermark: SyncWatermark | null;\n tombstones: ReadonlyArray<{\n containerId: string;\n documentId: string;\n updatedAt: string;\n }>;\n}\n\nexport interface ListedContainer {\n id: string;\n}\n\nexport interface ListContainersResponse {\n hasMore: boolean;\n items: ListedContainer[];\n nextWatermark: SyncWatermark | null;\n}\n\nexport interface ListContainerParentLanesResponse {\n results: ReadonlyArray<{\n laneId: string;\n page: ListContainersResponse;\n }>;\n}\n\nexport type ContainerDocumentLaneResult =\n | { readonly laneId: string; readonly page: ListContainerDocumentsResponse }\n | {\n readonly failure: { readonly code?: string | undefined };\n readonly laneId: string;\n };\n\nexport interface ContainerDocumentDiscoveryApi {\n readonly listContainerDocumentLanes: (\n input: ListContainerDocumentLanesRequest,\n options?: { readonly reportErrors?: boolean | undefined },\n ) => Promise<{\n readonly results: ReadonlyArray<ContainerDocumentLaneResult>;\n } | null>;\n readonly listContainerParentLanes: (\n input: ListContainerParentLanesRequest,\n ) => Promise<ListContainerParentLanesResponse | null>;\n}\n\n/**\n * One page per lane, aligned with the requested lanes: `null` for a lane the\n * server rejected, or `null` overall when the request itself failed.\n */\nexport type ContainerDocumentLaneLister = (\n lanes: ReadonlyArray<{\n readonly containerId: string;\n readonly watermark: SyncWatermark | null;\n }>,\n) => Promise<ReadonlyArray<ListContainerDocumentsResponse | null> | null>;\n\nexport interface DocumentLinkInput {\n accessEpoch?: number | undefined;\n containerIds: ReadonlyArray<string>;\n documentId: string;\n}\n\nexport type ContainerDocumentTombstone =\n ListContainerDocumentsResponse[\"tombstones\"][number];\n\nexport interface VerifiedContainerDocumentTombstone\n extends ContainerDocumentTombstone {\n /** The verified head's link-set epoch, not below the local document epoch. */\n readonly accessEpoch: number;\n /** The verified head link set; it omits `containerId`. */\n readonly linkedContainerIds: ReadonlyArray<string>;\n}\n\n/**\n * What the document's signed head says about a listing tombstone.\n * - `verified`: the verified head link set omits the container; apply.\n * - `refuted`: the verified head still links the container; keep visible and retry.\n * - `unverified`: no verified head is available; hold, hide, retry later.\n * - `deferred`: not attempted this run (the per-run head-load cap); hold\n * without counting an attempt, so the retry backoff does not grow.\n */\nexport type ContainerDocumentTombstoneVerdict =\n | {\n readonly kind: \"verified\";\n readonly tombstone: VerifiedContainerDocumentTombstone;\n }\n | {\n readonly kind: \"refuted\";\n /** The verified head link set, which contains the container. */\n readonly linkedContainerIds: ReadonlyArray<string>;\n readonly tombstone: ContainerDocumentTombstone;\n }\n | {\n readonly kind: \"unverified\";\n readonly tombstone: ContainerDocumentTombstone;\n }\n | {\n readonly kind: \"deferred\";\n readonly tombstone: ContainerDocumentTombstone;\n };\n\nexport interface HeldContainerDocumentTombstoneInput\n extends ContainerDocumentTombstone {\n /** Held without a verification attempt; the backoff does not advance. */\n readonly deferred?: boolean | undefined;\n /** A verified head still links it; retry the tombstone while keeping it visible. */\n readonly refuted?: boolean | undefined;\n}\n\nexport type ContainerDocumentTombstoneVerifier = (\n tombstones: ReadonlyArray<ContainerDocumentTombstone>,\n) => Promise<ReadonlyArray<ContainerDocumentTombstoneVerdict>>;\n\nexport type ContainerDocumentPlacement = Pick<\n ContainerDocumentTombstone,\n \"containerId\" | \"documentId\"\n>;\n\nexport interface ContainerDocumentTombstoneHoldStore {\n holdContainerDocumentTombstones: (\n tombstones: ReadonlyArray<HeldContainerDocumentTombstoneInput>,\n ) => Promise<void>;\n /** Held tombstones on these containers that are due for another attempt. */\n listHeldContainerDocumentTombstones: (\n containerIds: ReadonlyArray<string>,\n ) => Promise<ReadonlyArray<ContainerDocumentTombstone>>;\n /** The subset of placements that exist locally (link row or primary). */\n listKnownContainerDocumentPlacements: (\n placements: ReadonlyArray<ContainerDocumentPlacement>,\n ) => Promise<ReadonlyArray<ContainerDocumentPlacement>>;\n refuteContainerDocumentTombstoneHolds: (\n placements: ReadonlyArray<ContainerDocumentPlacement>,\n ) => Promise<void>;\n}\n\nexport interface DiscoverContainerDocumentsOptions\n extends ContainerDocumentTombstoneHoldStore {\n applyContainerDocumentTombstones: (\n tombstones: ReadonlyArray<VerifiedContainerDocumentTombstone>,\n ) => Promise<ReadonlyArray<DocumentSummary>>;\n verifyContainerDocumentTombstones: ContainerDocumentTombstoneVerifier;\n /** Allocate durable local order before fetching a listing. */\n beginDocumentDiscovery: () => Promise<number>;\n /** Persist pending candidates and return only signed placements. */\n verifyDiscoveredDocuments: (\n inputs: ReadonlyArray<DiscoveredDocumentCandidate>,\n containerIds: ReadonlyArray<string>,\n generation: number,\n tombstones?: ReadonlyArray<ContainerDocumentTombstone>,\n ) => Promise<{\n inputs: ReadonlyArray<DiscoveredDocumentInput>;\n /** False once a remote trust reset cancels this listing pass. */\n isCurrent: () => Promise<boolean>;\n /** Acknowledge only after local apply; true means no pending candidates remain. */\n commit: () => Promise<boolean>;\n }>;\n cacheReferencedPrincipalPolicies?: (\n references: ReadonlyArray<ReferencedPrincipalStateResponse>,\n ) => Promise<void>;\n containerId: string;\n /** Completed unwatermarked listing, published only after local apply succeeds. */\n onFullListing?: ((documentIds: ReadonlyArray<string>) => void) | undefined;\n loadContainerDocumentWatermark: (\n containerId: string,\n ) => Promise<SyncWatermark | null>;\n listContainerDocumentLanes: ContainerDocumentLaneLister;\n /** Recheck stillCurrent under the mutation lock before persisting a listing. */\n replaceDocumentLinksBatch: (\n inputs: ReadonlyArray<DocumentLinkInput>,\n stillCurrent: () => Promise<boolean>,\n ) => Promise<void>;\n saveContainerDocumentWatermark: (\n containerId: string,\n watermark: SyncWatermark,\n stillCurrent: () => Promise<boolean>,\n ) => Promise<void>;\n upsertDiscoveredDocuments: (\n inputs: ReadonlyArray<DiscoveredDocumentInput>,\n stillCurrent: () => Promise<boolean>,\n ) => Promise<ReadonlyArray<DocumentSummary>>;\n}\n\nexport interface ListedContainerDocuments {\n isFullListing: boolean;\n items: ListedContainerDocument[];\n nextWatermark: SyncWatermark | null;\n tombstones: ContainerDocumentTombstone[];\n}\n\nexport interface ContainerParentDiscoveryLane {\n parentId: string | null;\n watermark: SyncWatermark | null;\n}\n\nexport interface ListedContainerDocumentsLane {\n containerId: string;\n listedDocuments: ListedContainerDocuments | null;\n}\n\nexport interface DiscoverAllContainerDocumentsOptions\n extends Omit<DiscoverContainerDocumentsOptions, \"containerId\"> {\n containerIds: ReadonlyArray<string>;\n}\n\nexport interface RefreshAllContainerDocumentsOptions\n extends Omit<DiscoverAllContainerDocumentsOptions, \"containerIds\"> {\n listContainerParentLanes: ContainerDocumentDiscoveryApi[\"listContainerParentLanes\"];\n}\n\nexport interface RefreshAllContainerDocumentsFromApiOptions\n extends Omit<\n RefreshAllContainerDocumentsOptions,\n \"listContainerDocumentLanes\" | \"listContainerParentLanes\"\n > {\n readonly apiClient: Pick<\n ContainerDocumentDiscoveryApi,\n \"listContainerDocumentLanes\" | \"listContainerParentLanes\"\n >;\n}\n"]}
|
|
@@ -118,6 +118,16 @@ async function listDiscoveryLanes(input) {
|
|
|
118
118
|
listedDocuments: listed.get(containerId) ?? null,
|
|
119
119
|
}));
|
|
120
120
|
}
|
|
121
|
+
async function saveListedWatermarks(lanes, saveContainerDocumentWatermark, stillCurrent) {
|
|
122
|
+
await Promise.all(lanes.map(({ containerId, listedDocuments }) => listedDocuments
|
|
123
|
+
? saveAppliedContainerDocumentWatermark({
|
|
124
|
+
containerId,
|
|
125
|
+
listedDocuments,
|
|
126
|
+
saveContainerDocumentWatermark,
|
|
127
|
+
stillCurrent,
|
|
128
|
+
})
|
|
129
|
+
: undefined));
|
|
130
|
+
}
|
|
121
131
|
export async function discoverAllContainerDocuments(options) {
|
|
122
132
|
const { cacheReferencedPrincipalPolicies, containerIds, loadContainerDocumentWatermark, listContainerDocumentLanes, replaceDocumentLinksBatch, saveContainerDocumentWatermark, upsertDiscoveredDocuments, } = options;
|
|
123
133
|
const uniqueContainerIds = Array.from(new Set(containerIds)).filter((containerId) => typeof containerId === "string" && containerId.length > 0);
|
|
@@ -134,13 +144,13 @@ export async function discoverAllContainerDocuments(options) {
|
|
|
134
144
|
const discoveredDocumentInputs = verification.inputs;
|
|
135
145
|
const discoveredDocuments = discoveredDocumentInputs.length === 0
|
|
136
146
|
? []
|
|
137
|
-
: await upsertDiscoveredDocuments(discoveredDocumentInputs);
|
|
147
|
+
: await upsertDiscoveredDocuments(discoveredDocumentInputs, verification.isCurrent);
|
|
138
148
|
if (discoveredDocumentInputs.length > 0) {
|
|
139
149
|
await replaceDocumentLinksBatch(await withoutDeferredDocumentLinks(discoveredDocumentInputs.map((input) => ({
|
|
140
150
|
documentId: input.documentId,
|
|
141
151
|
accessEpoch: input.accessEpoch,
|
|
142
152
|
containerIds: input.linkedContainerIds,
|
|
143
|
-
})), discoveredDocuments));
|
|
153
|
+
})), discoveredDocuments), verification.isCurrent);
|
|
144
154
|
}
|
|
145
155
|
const tombstoneDocumentSummaries = await settleContainerDocumentTombstones({
|
|
146
156
|
containerIds: listedDocumentsByContainer.flatMap(({ containerId, listedDocuments }) => listedDocuments ? [containerId] : []),
|
|
@@ -150,13 +160,9 @@ export async function discoverAllContainerDocuments(options) {
|
|
|
150
160
|
const complete = await verification.commit();
|
|
151
161
|
if (!(await verification.isCurrent()))
|
|
152
162
|
return null;
|
|
153
|
-
await
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
listedDocuments,
|
|
157
|
-
saveContainerDocumentWatermark,
|
|
158
|
-
})
|
|
159
|
-
: undefined));
|
|
163
|
+
await saveListedWatermarks(listedDocumentsByContainer, saveContainerDocumentWatermark, verification.isCurrent);
|
|
164
|
+
if (!(await verification.isCurrent()))
|
|
165
|
+
return null;
|
|
160
166
|
return !complete &&
|
|
161
167
|
discoveredDocuments.length === 0 &&
|
|
162
168
|
tombstoneDocumentSummaries.length === 0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport { isDocumentUpdateCreatedEvent } from \"../../../data/documents/documentSync\";\nimport { dedupeReferencedPrincipalStates } from \"../../../data/keyingProjectionVerification/principalPolicy/principalPolicyCache\";\nimport { withoutDeferredDocumentLinks } from \"../documentLinks/deferredDocumentLinks\";\nimport { settleContainerDocumentTombstones } from \"../tombstones/documentTombstoneGate\";\nimport {\n listContainerDocumentLanesFromApi,\n listContainerDocumentsFromWatermarks,\n} from \"./containerDocumentListing\";\nimport { saveAppliedContainerDocumentWatermark } from \"./documentDiscoveryApply\";\nimport {\n collectApplicableDocumentTombstones,\n collectDiscoveredDocumentInputs,\n} from \"./documentDiscoveryInputs\";\nimport { discoverContainerDocumentLanes } from \"./documentDiscoveryLanes\";\nimport type {\n ContainerDocumentDiscoveryApi,\n ContainerParentDiscoveryLane,\n DiscoverAllContainerDocumentsOptions,\n DiscoverContainerDocumentsOptions,\n ListedContainerDocumentsLane,\n RefreshAllContainerDocumentsFromApiOptions,\n RefreshAllContainerDocumentsOptions,\n} from \"./documentDiscoveryTypes\";\n\nexport type { RefreshAllContainerDocumentsFromApiOptions } from \"./documentDiscoveryTypes\";\n\nconst CONTAINER_PARENT_DISCOVERY_BATCH_SIZE = 4;\n\ntype ContainerParentLaneRequest = Parameters<\n ContainerDocumentDiscoveryApi[\"listContainerParentLanes\"]\n>[0][\"lanes\"][number];\n\ninterface RequestedContainerParentLane {\n lane: ContainerParentDiscoveryLane;\n request: ContainerParentLaneRequest;\n}\n\nasync function listContainerParentLaneBatch(\n listContainerParentLanes: ContainerDocumentDiscoveryApi[\"listContainerParentLanes\"],\n requestedLanes: ReadonlyArray<RequestedContainerParentLane>,\n) {\n const response = await listContainerParentLanes({\n lanes: requestedLanes.map(({ request }) => request),\n });\n if (!response || response.results.length !== requestedLanes.length) {\n return null;\n }\n\n const requestedLaneIds = new Set(\n requestedLanes.map(({ request }) => request.laneId),\n );\n const pagesByLaneId = new Map<\n string,\n (typeof response.results)[number][\"page\"]\n >();\n for (const result of response.results) {\n if (\n !requestedLaneIds.has(result.laneId) ||\n pagesByLaneId.has(result.laneId)\n ) {\n return null;\n }\n pagesByLaneId.set(result.laneId, result.page);\n }\n\n return requestedLanes.map(({ lane, request }) => ({\n lane,\n response: pagesByLaneId.get(request.laneId),\n }));\n}\n\nfunction collectRemoteContainerIds(\n items: ReadonlyArray<{ id: string }>,\n seenContainerIds: Set<string>,\n containerIds: string[],\n): ReadonlyArray<string> {\n for (const { id } of items) {\n if (!seenContainerIds.has(id)) {\n seenContainerIds.add(id);\n containerIds.push(id);\n }\n }\n\n return items.map(({ id }) => id);\n}\n\nexport function hasUndiscoveredDocumentUpdateEvent(\n events: ReadonlyArray<unknown>,\n knownDocumentIds: ReadonlySet<string>,\n): boolean {\n return events.some(\n (event) =>\n isDocumentUpdateCreatedEvent(event) &&\n !knownDocumentIds.has(event.documentId),\n );\n}\n\nexport async function listAllRemoteContainerIds(\n listContainerParentLanes: ContainerDocumentDiscoveryApi[\"listContainerParentLanes\"],\n): Promise<ReadonlyArray<string> | null> {\n const containerIds: string[] = [];\n const queuedParentIds = new Set<string | null>();\n const seenContainerIds = new Set<string>();\n const lanes: ContainerParentDiscoveryLane[] = [];\n let nextLaneId = 0;\n const queueParentLane = (parentId: string | null) => {\n if (queuedParentIds.has(parentId)) {\n return;\n }\n\n queuedParentIds.add(parentId);\n lanes.push({ parentId, watermark: null });\n };\n\n queueParentLane(null);\n\n while (lanes.length > 0) {\n const laneBatch = lanes.splice(0, CONTAINER_PARENT_DISCOVERY_BATCH_SIZE);\n const requestedLanes = laneBatch.map((lane) => ({\n lane,\n request: {\n laneId: `container-parent-${nextLaneId++}`,\n parentId: lane.parentId,\n watermark: lane.watermark,\n },\n }));\n const listedLanes = await listContainerParentLaneBatch(\n listContainerParentLanes,\n requestedLanes,\n );\n if (!listedLanes) {\n return null;\n }\n const continuationLanes: ContainerParentDiscoveryLane[] = [];\n\n for (const { lane, response } of listedLanes) {\n if (!response) {\n return null;\n }\n\n for (const containerId of collectRemoteContainerIds(\n response.items,\n seenContainerIds,\n containerIds,\n )) {\n queueParentLane(containerId);\n }\n\n if (response.hasMore) {\n if (!response.nextWatermark) {\n return null;\n }\n continuationLanes.push({\n parentId: lane.parentId,\n watermark: response.nextWatermark,\n });\n }\n }\n\n lanes.unshift(...continuationLanes);\n }\n\n return containerIds;\n}\n\nexport function listAllRemoteContainerIdsFromApi(\n apiClient: Pick<ContainerDocumentDiscoveryApi, \"listContainerParentLanes\">,\n): Promise<ReadonlyArray<string> | null> {\n return listAllRemoteContainerIds((input) =>\n apiClient.listContainerParentLanes(input),\n );\n}\n\nexport async function discoverContainerDocuments({\n beginDocumentDiscovery,\n listContainerDocumentLanes,\n loadContainerDocumentWatermark,\n ...lane\n}: DiscoverContainerDocumentsOptions): Promise<ReadonlyArray<DocumentSummary> | null> {\n const [outcome] = await discoverContainerDocumentLanes({\n generation: await beginDocumentDiscovery(),\n lanes: [lane],\n listContainerDocumentLanes,\n loadContainerDocumentWatermark,\n });\n if (outcome?.status === \"rejected\") throw outcome.reason;\n return outcome?.value ?? null;\n}\n\nexport function discoverContainerDocumentsFromApi({\n apiClient,\n ...input\n}: Omit<DiscoverContainerDocumentsOptions, \"listContainerDocumentLanes\"> & {\n readonly apiClient: Pick<\n ContainerDocumentDiscoveryApi,\n \"listContainerDocumentLanes\"\n >;\n}): Promise<ReadonlyArray<DocumentSummary> | null> {\n return discoverContainerDocuments({\n ...input,\n listContainerDocumentLanes: (lanes) =>\n listContainerDocumentLanesFromApi(apiClient, lanes),\n });\n}\n\nasync function listDiscoveryLanes(\n input: Parameters<typeof listContainerDocumentsFromWatermarks>[0],\n): Promise<ListedContainerDocumentsLane[]> {\n const listed = await listContainerDocumentsFromWatermarks(input);\n return input.containerIds.map((containerId) => ({\n containerId,\n listedDocuments: listed.get(containerId) ?? null,\n }));\n}\n\nexport async function discoverAllContainerDocuments(\n options: DiscoverAllContainerDocumentsOptions,\n): Promise<ReadonlyArray<DocumentSummary> | null> {\n const {\n cacheReferencedPrincipalPolicies,\n containerIds,\n loadContainerDocumentWatermark,\n listContainerDocumentLanes,\n replaceDocumentLinksBatch,\n saveContainerDocumentWatermark,\n upsertDiscoveredDocuments,\n } = options;\n const uniqueContainerIds = Array.from(new Set(containerIds)).filter(\n (containerId): containerId is string =>\n typeof containerId === \"string\" && containerId.length > 0,\n );\n const generation = await options.beginDocumentDiscovery();\n const listedDocumentsByContainer = await listDiscoveryLanes({\n containerIds: uniqueContainerIds,\n listContainerDocumentLanes,\n loadContainerDocumentWatermark,\n });\n await cacheReferencedPrincipalPolicies?.(\n dedupeReferencedPrincipalStates(\n listedDocumentsByContainer.flatMap(\n ({ listedDocuments }) =>\n listedDocuments?.items.flatMap(\n (document) => document.referencedPrincipals,\n ) ?? [],\n ),\n ),\n );\n const verification = await options.verifyDiscoveredDocuments(\n collectDiscoveredDocumentInputs(listedDocumentsByContainer),\n uniqueContainerIds,\n generation,\n collectApplicableDocumentTombstones(listedDocumentsByContainer),\n );\n if (!(await verification.isCurrent())) return null;\n const discoveredDocumentInputs = verification.inputs;\n const discoveredDocuments =\n discoveredDocumentInputs.length === 0\n ? []\n : await upsertDiscoveredDocuments(discoveredDocumentInputs);\n\n if (discoveredDocumentInputs.length > 0) {\n await replaceDocumentLinksBatch(\n await withoutDeferredDocumentLinks(\n discoveredDocumentInputs.map((input) => ({\n documentId: input.documentId,\n accessEpoch: input.accessEpoch,\n containerIds: input.linkedContainerIds,\n })),\n discoveredDocuments,\n ),\n );\n }\n\n const tombstoneDocumentSummaries = await settleContainerDocumentTombstones({\n containerIds: listedDocumentsByContainer.flatMap(\n ({ containerId, listedDocuments }) =>\n listedDocuments ? [containerId] : [],\n ),\n store: options,\n tombstones: collectApplicableDocumentTombstones(listedDocumentsByContainer),\n });\n\n const complete = await verification.commit();\n if (!(await verification.isCurrent())) return null;\n\n await Promise.all(\n listedDocumentsByContainer.map(({ containerId, listedDocuments }) =>\n listedDocuments\n ? saveAppliedContainerDocumentWatermark({\n containerId,\n listedDocuments,\n saveContainerDocumentWatermark,\n })\n : undefined,\n ),\n );\n\n return !complete &&\n discoveredDocuments.length === 0 &&\n tombstoneDocumentSummaries.length === 0\n ? null\n : [...discoveredDocuments, ...tombstoneDocumentSummaries];\n}\n\nasync function refreshAllContainerDocuments({\n listContainerParentLanes,\n ...input\n}: RefreshAllContainerDocumentsOptions): Promise<ReadonlyArray<DocumentSummary> | null> {\n const containerIds = await listAllRemoteContainerIds(\n listContainerParentLanes,\n );\n if (!containerIds) {\n return null;\n }\n\n return discoverAllContainerDocuments({\n ...input,\n containerIds,\n });\n}\n\nexport function refreshAllContainerDocumentsFromApi({\n apiClient,\n ...input\n}: RefreshAllContainerDocumentsFromApiOptions): Promise<ReadonlyArray<DocumentSummary> | null> {\n return refreshAllContainerDocuments({\n ...input,\n listContainerDocumentLanes: (lanes) =>\n listContainerDocumentLanesFromApi(apiClient, lanes),\n listContainerParentLanes: (input) =>\n apiClient.listContainerParentLanes(input),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport { isDocumentUpdateCreatedEvent } from \"../../../data/documents/documentSync\";\nimport { dedupeReferencedPrincipalStates } from \"../../../data/keyingProjectionVerification/principalPolicy/principalPolicyCache\";\nimport { withoutDeferredDocumentLinks } from \"../documentLinks/deferredDocumentLinks\";\nimport { settleContainerDocumentTombstones } from \"../tombstones/documentTombstoneGate\";\nimport {\n listContainerDocumentLanesFromApi,\n listContainerDocumentsFromWatermarks,\n} from \"./containerDocumentListing\";\nimport { saveAppliedContainerDocumentWatermark } from \"./documentDiscoveryApply\";\nimport {\n collectApplicableDocumentTombstones,\n collectDiscoveredDocumentInputs,\n} from \"./documentDiscoveryInputs\";\nimport { discoverContainerDocumentLanes } from \"./documentDiscoveryLanes\";\nimport type {\n ContainerDocumentDiscoveryApi,\n ContainerParentDiscoveryLane,\n DiscoverAllContainerDocumentsOptions,\n DiscoverContainerDocumentsOptions,\n ListedContainerDocumentsLane,\n RefreshAllContainerDocumentsFromApiOptions,\n RefreshAllContainerDocumentsOptions,\n} from \"./documentDiscoveryTypes\";\n\nexport type { RefreshAllContainerDocumentsFromApiOptions } from \"./documentDiscoveryTypes\";\n\nconst CONTAINER_PARENT_DISCOVERY_BATCH_SIZE = 4;\n\ntype ContainerParentLaneRequest = Parameters<\n ContainerDocumentDiscoveryApi[\"listContainerParentLanes\"]\n>[0][\"lanes\"][number];\n\ninterface RequestedContainerParentLane {\n lane: ContainerParentDiscoveryLane;\n request: ContainerParentLaneRequest;\n}\n\nasync function listContainerParentLaneBatch(\n listContainerParentLanes: ContainerDocumentDiscoveryApi[\"listContainerParentLanes\"],\n requestedLanes: ReadonlyArray<RequestedContainerParentLane>,\n) {\n const response = await listContainerParentLanes({\n lanes: requestedLanes.map(({ request }) => request),\n });\n if (!response || response.results.length !== requestedLanes.length) {\n return null;\n }\n\n const requestedLaneIds = new Set(\n requestedLanes.map(({ request }) => request.laneId),\n );\n const pagesByLaneId = new Map<\n string,\n (typeof response.results)[number][\"page\"]\n >();\n for (const result of response.results) {\n if (\n !requestedLaneIds.has(result.laneId) ||\n pagesByLaneId.has(result.laneId)\n ) {\n return null;\n }\n pagesByLaneId.set(result.laneId, result.page);\n }\n\n return requestedLanes.map(({ lane, request }) => ({\n lane,\n response: pagesByLaneId.get(request.laneId),\n }));\n}\n\nfunction collectRemoteContainerIds(\n items: ReadonlyArray<{ id: string }>,\n seenContainerIds: Set<string>,\n containerIds: string[],\n): ReadonlyArray<string> {\n for (const { id } of items) {\n if (!seenContainerIds.has(id)) {\n seenContainerIds.add(id);\n containerIds.push(id);\n }\n }\n\n return items.map(({ id }) => id);\n}\n\nexport function hasUndiscoveredDocumentUpdateEvent(\n events: ReadonlyArray<unknown>,\n knownDocumentIds: ReadonlySet<string>,\n): boolean {\n return events.some(\n (event) =>\n isDocumentUpdateCreatedEvent(event) &&\n !knownDocumentIds.has(event.documentId),\n );\n}\n\nexport async function listAllRemoteContainerIds(\n listContainerParentLanes: ContainerDocumentDiscoveryApi[\"listContainerParentLanes\"],\n): Promise<ReadonlyArray<string> | null> {\n const containerIds: string[] = [];\n const queuedParentIds = new Set<string | null>();\n const seenContainerIds = new Set<string>();\n const lanes: ContainerParentDiscoveryLane[] = [];\n let nextLaneId = 0;\n const queueParentLane = (parentId: string | null) => {\n if (queuedParentIds.has(parentId)) {\n return;\n }\n\n queuedParentIds.add(parentId);\n lanes.push({ parentId, watermark: null });\n };\n\n queueParentLane(null);\n\n while (lanes.length > 0) {\n const laneBatch = lanes.splice(0, CONTAINER_PARENT_DISCOVERY_BATCH_SIZE);\n const requestedLanes = laneBatch.map((lane) => ({\n lane,\n request: {\n laneId: `container-parent-${nextLaneId++}`,\n parentId: lane.parentId,\n watermark: lane.watermark,\n },\n }));\n const listedLanes = await listContainerParentLaneBatch(\n listContainerParentLanes,\n requestedLanes,\n );\n if (!listedLanes) {\n return null;\n }\n const continuationLanes: ContainerParentDiscoveryLane[] = [];\n\n for (const { lane, response } of listedLanes) {\n if (!response) {\n return null;\n }\n\n for (const containerId of collectRemoteContainerIds(\n response.items,\n seenContainerIds,\n containerIds,\n )) {\n queueParentLane(containerId);\n }\n\n if (response.hasMore) {\n if (!response.nextWatermark) {\n return null;\n }\n continuationLanes.push({\n parentId: lane.parentId,\n watermark: response.nextWatermark,\n });\n }\n }\n\n lanes.unshift(...continuationLanes);\n }\n\n return containerIds;\n}\n\nexport function listAllRemoteContainerIdsFromApi(\n apiClient: Pick<ContainerDocumentDiscoveryApi, \"listContainerParentLanes\">,\n): Promise<ReadonlyArray<string> | null> {\n return listAllRemoteContainerIds((input) =>\n apiClient.listContainerParentLanes(input),\n );\n}\n\nexport async function discoverContainerDocuments({\n beginDocumentDiscovery,\n listContainerDocumentLanes,\n loadContainerDocumentWatermark,\n ...lane\n}: DiscoverContainerDocumentsOptions): Promise<ReadonlyArray<DocumentSummary> | null> {\n const [outcome] = await discoverContainerDocumentLanes({\n generation: await beginDocumentDiscovery(),\n lanes: [lane],\n listContainerDocumentLanes,\n loadContainerDocumentWatermark,\n });\n if (outcome?.status === \"rejected\") throw outcome.reason;\n return outcome?.value ?? null;\n}\n\nexport function discoverContainerDocumentsFromApi({\n apiClient,\n ...input\n}: Omit<DiscoverContainerDocumentsOptions, \"listContainerDocumentLanes\"> & {\n readonly apiClient: Pick<\n ContainerDocumentDiscoveryApi,\n \"listContainerDocumentLanes\"\n >;\n}): Promise<ReadonlyArray<DocumentSummary> | null> {\n return discoverContainerDocuments({\n ...input,\n listContainerDocumentLanes: (lanes) =>\n listContainerDocumentLanesFromApi(apiClient, lanes),\n });\n}\n\nasync function listDiscoveryLanes(\n input: Parameters<typeof listContainerDocumentsFromWatermarks>[0],\n): Promise<ListedContainerDocumentsLane[]> {\n const listed = await listContainerDocumentsFromWatermarks(input);\n return input.containerIds.map((containerId) => ({\n containerId,\n listedDocuments: listed.get(containerId) ?? null,\n }));\n}\n\nasync function saveListedWatermarks(\n lanes: readonly ListedContainerDocumentsLane[],\n saveContainerDocumentWatermark: DiscoverContainerDocumentsOptions[\"saveContainerDocumentWatermark\"],\n stillCurrent: () => Promise<boolean>,\n): Promise<void> {\n await Promise.all(\n lanes.map(({ containerId, listedDocuments }) =>\n listedDocuments\n ? saveAppliedContainerDocumentWatermark({\n containerId,\n listedDocuments,\n saveContainerDocumentWatermark,\n stillCurrent,\n })\n : undefined,\n ),\n );\n}\n\nexport async function discoverAllContainerDocuments(\n options: DiscoverAllContainerDocumentsOptions,\n): Promise<ReadonlyArray<DocumentSummary> | null> {\n const {\n cacheReferencedPrincipalPolicies,\n containerIds,\n loadContainerDocumentWatermark,\n listContainerDocumentLanes,\n replaceDocumentLinksBatch,\n saveContainerDocumentWatermark,\n upsertDiscoveredDocuments,\n } = options;\n const uniqueContainerIds = Array.from(new Set(containerIds)).filter(\n (containerId): containerId is string =>\n typeof containerId === \"string\" && containerId.length > 0,\n );\n const generation = await options.beginDocumentDiscovery();\n const listedDocumentsByContainer = await listDiscoveryLanes({\n containerIds: uniqueContainerIds,\n listContainerDocumentLanes,\n loadContainerDocumentWatermark,\n });\n await cacheReferencedPrincipalPolicies?.(\n dedupeReferencedPrincipalStates(\n listedDocumentsByContainer.flatMap(\n ({ listedDocuments }) =>\n listedDocuments?.items.flatMap(\n (document) => document.referencedPrincipals,\n ) ?? [],\n ),\n ),\n );\n const verification = await options.verifyDiscoveredDocuments(\n collectDiscoveredDocumentInputs(listedDocumentsByContainer),\n uniqueContainerIds,\n generation,\n collectApplicableDocumentTombstones(listedDocumentsByContainer),\n );\n if (!(await verification.isCurrent())) return null;\n const discoveredDocumentInputs = verification.inputs;\n const discoveredDocuments =\n discoveredDocumentInputs.length === 0\n ? []\n : await upsertDiscoveredDocuments(\n discoveredDocumentInputs,\n verification.isCurrent,\n );\n\n if (discoveredDocumentInputs.length > 0) {\n await replaceDocumentLinksBatch(\n await withoutDeferredDocumentLinks(\n discoveredDocumentInputs.map((input) => ({\n documentId: input.documentId,\n accessEpoch: input.accessEpoch,\n containerIds: input.linkedContainerIds,\n })),\n discoveredDocuments,\n ),\n verification.isCurrent,\n );\n }\n\n const tombstoneDocumentSummaries = await settleContainerDocumentTombstones({\n containerIds: listedDocumentsByContainer.flatMap(\n ({ containerId, listedDocuments }) =>\n listedDocuments ? [containerId] : [],\n ),\n store: options,\n tombstones: collectApplicableDocumentTombstones(listedDocumentsByContainer),\n });\n\n const complete = await verification.commit();\n if (!(await verification.isCurrent())) return null;\n\n await saveListedWatermarks(\n listedDocumentsByContainer,\n saveContainerDocumentWatermark,\n verification.isCurrent,\n );\n if (!(await verification.isCurrent())) return null;\n\n return !complete &&\n discoveredDocuments.length === 0 &&\n tombstoneDocumentSummaries.length === 0\n ? null\n : [...discoveredDocuments, ...tombstoneDocumentSummaries];\n}\n\nasync function refreshAllContainerDocuments({\n listContainerParentLanes,\n ...input\n}: RefreshAllContainerDocumentsOptions): Promise<ReadonlyArray<DocumentSummary> | null> {\n const containerIds = await listAllRemoteContainerIds(\n listContainerParentLanes,\n );\n if (!containerIds) {\n return null;\n }\n\n return discoverAllContainerDocuments({\n ...input,\n containerIds,\n });\n}\n\nexport function refreshAllContainerDocumentsFromApi({\n apiClient,\n ...input\n}: RefreshAllContainerDocumentsFromApiOptions): Promise<ReadonlyArray<DocumentSummary> | null> {\n return refreshAllContainerDocuments({\n ...input,\n listContainerDocumentLanes: (lanes) =>\n listContainerDocumentLanesFromApi(apiClient, lanes),\n listContainerParentLanes: (input) =>\n apiClient.listContainerParentLanes(input),\n });\n}\n"]}
|
|
@@ -3,22 +3,23 @@ import { sqlDocumentContainerProjectionPersistence } from "../../../data/persist
|
|
|
3
3
|
import { upsertDiscoveredDocuments } from "../../../data/persistence/documents/documentsPersistence.js";
|
|
4
4
|
import { runSerializedSqlMutation } from "../../../data/sqlite/sqlExec.js";
|
|
5
5
|
/**
|
|
6
|
-
* Binds a discovery lane's writes to its
|
|
7
|
-
* unpaused.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* holds. A stale listing therefore never restores documents, links, or the
|
|
6
|
+
* Binds a discovery lane's writes to its generation and its organization staying
|
|
7
|
+
* unpaused. Access changes and pauses invalidate in-flight listings, so the
|
|
8
|
+
* apply stops at its next check. Each write already started rechecks under
|
|
9
|
+
* the SQLite mutation lock held by the reset or pause cleanup. A stale listing therefore never restores documents, links, or the
|
|
11
10
|
* watermark the cleanup removed; a watermark saved after the cleanup would
|
|
12
11
|
* otherwise make the listing after a resume skip every unchanged document.
|
|
13
12
|
*/
|
|
14
|
-
export function
|
|
15
|
-
const
|
|
13
|
+
export function guardDocumentDiscoveryWrites(input) {
|
|
14
|
+
const whileCurrent = (skipped, write, stillCurrent) => runSerializedSqlMutation(input.execSql, async (lockedExecSql) => input.isPaused() || !(await stillCurrent())
|
|
15
|
+
? skipped
|
|
16
|
+
: write(lockedExecSql));
|
|
16
17
|
return {
|
|
17
|
-
replaceDocumentLinksBatch: (inputs) =>
|
|
18
|
-
saveContainerDocumentWatermark: (containerId, watermark) =>
|
|
18
|
+
replaceDocumentLinksBatch: (inputs, stillCurrent) => whileCurrent(undefined, (lockedExecSql) => sqlDocumentContainerProjectionPersistence.replaceDocumentLinksBatch(lockedExecSql, inputs), stillCurrent),
|
|
19
|
+
saveContainerDocumentWatermark: (containerId, watermark, stillCurrent) => whileCurrent(undefined, async (lockedExecSql) => {
|
|
19
20
|
await sqlContainerSyncWatermarkPersistence.saveWatermark(lockedExecSql, containerContentsSyncLane(containerId), watermark);
|
|
20
|
-
}),
|
|
21
|
-
upsertDiscoveredDocuments: (inputs) =>
|
|
21
|
+
}, stillCurrent),
|
|
22
|
+
upsertDiscoveredDocuments: (inputs, stillCurrent) => whileCurrent([], (lockedExecSql) => upsertDiscoveredDocuments(lockedExecSql, inputs), stillCurrent),
|
|
22
23
|
async verifyDiscoveredDocuments(...args) {
|
|
23
24
|
const verification = await input.verifyDiscoveredDocuments(...args);
|
|
24
25
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discoveryGuard.js","sourceRoot":"","sources":["discoveryGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,oCAAoC,GACrC,MAAM,gFAAgF,CAAC;AACxF,OAAO,EAAE,yCAAyC,EAAE,MAAM,qFAAqF,CAAC;AAChJ,OAAO,EAAE,yBAAyB,EAAE,MAAM,0DAA0D,CAAC;AACrG,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAYxE
|
|
1
|
+
{"version":3,"file":"discoveryGuard.js","sourceRoot":"","sources":["discoveryGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,oCAAoC,GACrC,MAAM,gFAAgF,CAAC;AACxF,OAAO,EAAE,yCAAyC,EAAE,MAAM,qFAAqF,CAAC;AAChJ,OAAO,EAAE,yBAAyB,EAAE,MAAM,0DAA0D,CAAC;AACrG,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAYxE;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAAC,KAI5C;IACC,MAAM,YAAY,GAAG,CACnB,OAAU,EACV,KAA6C,EAC7C,YAAoC,EACxB,EAAE,CACd,wBAAwB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,CAC9D,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,MAAM,YAAY,EAAE,CAAC;QACzC,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CACzB,CAAC;IACJ,OAAO;QACL,yBAAyB,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,CAClD,YAAY,CACV,SAAS,EACT,CAAC,aAAa,EAAE,EAAE,CAChB,yCAAyC,CAAC,yBAAyB,CACjE,aAAa,EACb,MAAM,CACP,EACH,YAAY,CACb;QACH,8BAA8B,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,CACvE,YAAY,CACV,SAAS,EACT,KAAK,EAAE,aAAa,EAAE,EAAE;YACtB,MAAM,oCAAoC,CAAC,aAAa,CACtD,aAAa,EACb,yBAAyB,CAAC,WAAW,CAAC,EACtC,SAAS,CACV,CAAC;QACJ,CAAC,EACD,YAAY,CACb;QACH,yBAAyB,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,CAClD,YAAY,CACV,EAAE,EACF,CAAC,aAAa,EAAE,EAAE,CAAC,yBAAyB,CAAC,aAAa,EAAE,MAAM,CAAC,EACnE,YAAY,CACb;QACH,KAAK,CAAC,yBAAyB,CAAC,GAAG,IAAI;YACrC,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,yBAAyB,CAAC,GAAG,IAAI,CAAC,CAAC;YACpE,OAAO;gBACL,GAAG,YAAY;gBACf,SAAS,EAAE,KAAK,IAAI,EAAE,CACpB,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,YAAY,CAAC,SAAS,EAAE,CAAC;aACxD,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import {\n containerContentsSyncLane,\n sqlContainerSyncWatermarkPersistence,\n} from \"../../../data/persistence/container-contents/containerSyncWatermarkPersistence\";\nimport { sqlDocumentContainerProjectionPersistence } from \"../../../data/persistence/container-contents/documentContainerProjectionPersistence\";\nimport { upsertDiscoveredDocuments } from \"../../../data/persistence/documents/documentsPersistence\";\nimport { runSerializedSqlMutation } from \"../../../data/sqlite/sqlExec\";\nimport type { ExecSql } from \"../../../data/sqlite/sqlSchema\";\nimport type { DiscoverContainerDocumentsOptions } from \"../documentDiscovery/documentDiscoveryTypes\";\n\ntype PauseGuardedDiscovery = Pick<\n DiscoverContainerDocumentsOptions,\n | \"replaceDocumentLinksBatch\"\n | \"saveContainerDocumentWatermark\"\n | \"upsertDiscoveredDocuments\"\n | \"verifyDiscoveredDocuments\"\n>;\n\n/**\n * Binds a discovery lane's writes to its generation and its organization staying\n * unpaused. Access changes and pauses invalidate in-flight listings, so the\n * apply stops at its next check. Each write already started rechecks under\n * the SQLite mutation lock held by the reset or pause cleanup. A stale listing therefore never restores documents, links, or the\n * watermark the cleanup removed; a watermark saved after the cleanup would\n * otherwise make the listing after a resume skip every unchanged document.\n */\nexport function guardDocumentDiscoveryWrites(input: {\n readonly execSql: ExecSql;\n readonly isPaused: () => boolean;\n readonly verifyDiscoveredDocuments: DiscoverContainerDocumentsOptions[\"verifyDiscoveredDocuments\"];\n}): PauseGuardedDiscovery {\n const whileCurrent = <T>(\n skipped: T,\n write: (lockedExecSql: ExecSql) => Promise<T>,\n stillCurrent: () => Promise<boolean>,\n ): Promise<T> =>\n runSerializedSqlMutation(input.execSql, async (lockedExecSql) =>\n input.isPaused() || !(await stillCurrent())\n ? skipped\n : write(lockedExecSql),\n );\n return {\n replaceDocumentLinksBatch: (inputs, stillCurrent) =>\n whileCurrent(\n undefined,\n (lockedExecSql) =>\n sqlDocumentContainerProjectionPersistence.replaceDocumentLinksBatch(\n lockedExecSql,\n inputs,\n ),\n stillCurrent,\n ),\n saveContainerDocumentWatermark: (containerId, watermark, stillCurrent) =>\n whileCurrent(\n undefined,\n async (lockedExecSql) => {\n await sqlContainerSyncWatermarkPersistence.saveWatermark(\n lockedExecSql,\n containerContentsSyncLane(containerId),\n watermark,\n );\n },\n stillCurrent,\n ),\n upsertDiscoveredDocuments: (inputs, stillCurrent) =>\n whileCurrent(\n [],\n (lockedExecSql) => upsertDiscoveredDocuments(lockedExecSql, inputs),\n stillCurrent,\n ),\n async verifyDiscoveredDocuments(...args) {\n const verification = await input.verifyDiscoveredDocuments(...args);\n return {\n ...verification,\n isCurrent: async () =>\n !input.isPaused() && (await verification.isCurrent()),\n };\n },\n };\n}\n"]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ContainerAccessLevel } from "../../../../internal/crypto/index.js";
|
|
2
2
|
import type { ContainerMutationResponse, ContainerWriterProjectionResponse } from "../../../../internal/validators/response/index.js";
|
|
3
3
|
import type { ContainerMutationAuthor, ContainerShareApi, ContainerSharePlan } from "../../../../data/containers/shared/types.js";
|
|
4
4
|
import { type ProjectionUserKeyResolver, type ReferencedPrincipalPolicyWarmer } from "../../../../data/keyingProjectionVerification/index.js";
|
|
5
5
|
import type { SecurityIncidentReporter } from "../../../../data/shared/securityIncidents.js";
|
|
6
6
|
import type { ExecSql } from "../../../../data/sqlite/sqlSchema.js";
|
|
7
7
|
import { type TrustedUserIdentityResolver } from "../../../../data/trustedUserIdentity/index.js";
|
|
8
|
-
|
|
8
|
+
type ShareRemoteContainerInput = {
|
|
9
9
|
reportSecurityIncident: SecurityIncidentReporter;
|
|
10
10
|
accessLevel: ContainerAccessLevel;
|
|
11
11
|
apiClient: ContainerShareApi;
|
|
@@ -21,8 +21,18 @@ export declare function shareRemoteContainer(input: {
|
|
|
21
21
|
stillCurrent?: (() => boolean) | undefined;
|
|
22
22
|
targetSecretKey: Uint8Array;
|
|
23
23
|
warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;
|
|
24
|
-
}
|
|
24
|
+
};
|
|
25
|
+
type SharedRemoteContainer = {
|
|
25
26
|
containerKey: Uint8Array;
|
|
26
27
|
plan: ContainerSharePlan;
|
|
27
28
|
response: ContainerMutationResponse;
|
|
28
|
-
}
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Share a container with a user. A head on the signed path can advance while
|
|
32
|
+
* the share is in flight, most often under this device's own background
|
|
33
|
+
* re-citation of held descendants after a share above it. The API refuses
|
|
34
|
+
* that as a stale path, and the share is planned once more from a fresh
|
|
35
|
+
* projection, as a create under a stale parent is.
|
|
36
|
+
*/
|
|
37
|
+
export declare function shareRemoteContainer(input: ShareRemoteContainerInput): Promise<SharedRemoteContainer | null>;
|
|
38
|
+
export {};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { KeyingVerificationError, } from "../../../../internal/crypto/index.js";
|
|
2
|
+
import { isStaleParentContainerPathFailure } from "../../../../data/containers/shared/mutationFailures.js";
|
|
1
3
|
import { nullOnProjectionVerificationCancellation, requireProjectionUserKeyResolver, } from "../../../../data/keyingProjectionVerification/index.js";
|
|
2
4
|
import { requireTrustedUserIdentityResolver, } from "../../../../data/trustedUserIdentity/index.js";
|
|
3
5
|
import { submitAcknowledgedContainerMutation, submitPlainContainerMutation, } from "../mutationSubmit.js";
|
|
@@ -6,36 +8,68 @@ import { resolveShareProjection } from "./shareProjection.js";
|
|
|
6
8
|
async function buildCurrentContainerSharePlan(input) {
|
|
7
9
|
return nullOnProjectionVerificationCancellation(() => buildMaterializedContainerSharePlan(input));
|
|
8
10
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
/** A path a head advanced under, which a share plans once more (#2647). */
|
|
12
|
+
const STALE_PATH = Symbol("stale-path");
|
|
13
|
+
/**
|
|
14
|
+
* Submit through the status-bearing endpoint when the adapter has one. The
|
|
15
|
+
* refusal that ends the share is reported once submission settles, as the
|
|
16
|
+
* plain endpoint's would be; a stale path the share plans again is not.
|
|
17
|
+
*/
|
|
18
|
+
async function submitShare(input, request, retriesStalePath) {
|
|
19
|
+
const options = {
|
|
20
|
+
expectedPaymentRequiredOrganizationId: input.author.organizationId,
|
|
21
|
+
};
|
|
22
|
+
const { shareContainerResult } = input.apiClient;
|
|
23
|
+
if (!shareContainerResult) {
|
|
24
|
+
return submitPlainContainerMutation(() => input.apiClient.shareContainer(input.containerId, request, options));
|
|
25
|
+
}
|
|
26
|
+
const result = await shareContainerResult.call(input.apiClient, input.containerId, request, { ...options, reportErrors: false });
|
|
27
|
+
if (result.ok)
|
|
28
|
+
return { ok: true, data: result.data };
|
|
29
|
+
return retriesStalePath && isStaleParentContainerPathFailure(result)
|
|
30
|
+
? { ok: false, code: result.code, status: result.status }
|
|
31
|
+
: result;
|
|
32
|
+
}
|
|
33
|
+
function planShare(input, recipientEncapsulationPublicKey, resolveProjectionUserKey) {
|
|
34
|
+
return resolveShareProjection(input, resolveProjectionUserKey).then((previousProjection) => previousProjection &&
|
|
35
|
+
buildCurrentContainerSharePlan({
|
|
36
|
+
accessLevel: input.accessLevel,
|
|
37
|
+
author: input.author,
|
|
38
|
+
eventId: input.eventId,
|
|
39
|
+
execSql: input.execSql,
|
|
40
|
+
previousProjection,
|
|
41
|
+
recipient: {
|
|
42
|
+
recipientEncapsulationPublicKey,
|
|
43
|
+
subjectId: input.recipientUserId,
|
|
44
|
+
subjectType: "user",
|
|
45
|
+
},
|
|
46
|
+
resolveProjectionUserKey,
|
|
47
|
+
signedAt: input.signedAt,
|
|
48
|
+
stillCurrent: input.stillCurrent,
|
|
49
|
+
targetSecretKey: input.targetSecretKey,
|
|
50
|
+
warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
async function shareRemoteContainerOnce(input, recipientEncapsulationPublicKey, resolveProjectionUserKey, retriesStalePath) {
|
|
54
|
+
let materializedPlan;
|
|
55
|
+
try {
|
|
56
|
+
materializedPlan = await planShare(input, recipientEncapsulationPublicKey, resolveProjectionUserKey);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// The path the share was handed predates a pin this device has since
|
|
60
|
+
// advanced, most often through its own re-citation. A fresh projection
|
|
61
|
+
// is held to the same pins, so a server that rolls a head back still
|
|
62
|
+
// fails the share, on its second plan.
|
|
63
|
+
if (retriesStalePath &&
|
|
64
|
+
error instanceof KeyingVerificationError &&
|
|
65
|
+
error.code === "rollback")
|
|
66
|
+
return STALE_PATH;
|
|
67
|
+
throw error;
|
|
15
68
|
}
|
|
16
|
-
const previousProjection = await resolveShareProjection(input, resolveProjectionUserKey);
|
|
17
|
-
if (!previousProjection)
|
|
18
|
-
return null;
|
|
19
|
-
const materializedPlan = await buildCurrentContainerSharePlan({
|
|
20
|
-
accessLevel: input.accessLevel,
|
|
21
|
-
author: input.author,
|
|
22
|
-
eventId: input.eventId,
|
|
23
|
-
execSql: input.execSql,
|
|
24
|
-
previousProjection,
|
|
25
|
-
recipient: {
|
|
26
|
-
recipientEncapsulationPublicKey: recipientIdentity.encapsulationPublicKey,
|
|
27
|
-
subjectId: input.recipientUserId,
|
|
28
|
-
subjectType: "user",
|
|
29
|
-
},
|
|
30
|
-
resolveProjectionUserKey,
|
|
31
|
-
signedAt: input.signedAt,
|
|
32
|
-
stillCurrent: input.stillCurrent,
|
|
33
|
-
targetSecretKey: input.targetSecretKey,
|
|
34
|
-
warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,
|
|
35
|
-
});
|
|
36
69
|
if (!materializedPlan)
|
|
37
70
|
return null;
|
|
38
|
-
|
|
71
|
+
let stalePath = false;
|
|
72
|
+
const shared = await submitAcknowledgedContainerMutation({
|
|
39
73
|
reportSecurityIncident: input.reportSecurityIncident,
|
|
40
74
|
recitationPolicies: [],
|
|
41
75
|
apiClient: input.apiClient,
|
|
@@ -44,9 +78,38 @@ export async function shareRemoteContainer(input) {
|
|
|
44
78
|
execSql: input.execSql,
|
|
45
79
|
plan: materializedPlan.plan,
|
|
46
80
|
stillCurrent: input.stillCurrent,
|
|
47
|
-
submit:
|
|
48
|
-
|
|
49
|
-
|
|
81
|
+
submit: async () => {
|
|
82
|
+
const result = await submitShare(input, materializedPlan.plan.request, retriesStalePath);
|
|
83
|
+
stalePath =
|
|
84
|
+
!result.ok &&
|
|
85
|
+
retriesStalePath &&
|
|
86
|
+
isStaleParentContainerPathFailure(result);
|
|
87
|
+
return result;
|
|
88
|
+
},
|
|
50
89
|
});
|
|
90
|
+
return shared ?? (stalePath ? STALE_PATH : null);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Share a container with a user. A head on the signed path can advance while
|
|
94
|
+
* the share is in flight, most often under this device's own background
|
|
95
|
+
* re-citation of held descendants after a share above it. The API refuses
|
|
96
|
+
* that as a stale path, and the share is planned once more from a fresh
|
|
97
|
+
* projection, as a create under a stale parent is.
|
|
98
|
+
*/
|
|
99
|
+
export async function shareRemoteContainer(input) {
|
|
100
|
+
const resolveProjectionUserKey = requireProjectionUserKeyResolver(input.resolveProjectionUserKey, "Remote container share");
|
|
101
|
+
const resolveTrustedUserIdentity = requireTrustedUserIdentityResolver(input.resolveTrustedUserIdentity);
|
|
102
|
+
const recipientIdentity = await resolveTrustedUserIdentity(input.recipientUserId);
|
|
103
|
+
if (!recipientIdentity || input.stillCurrent?.() === false) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const first = await shareRemoteContainerOnce(input, recipientIdentity.encapsulationPublicKey, resolveProjectionUserKey, true);
|
|
107
|
+
if (first !== STALE_PATH)
|
|
108
|
+
return first;
|
|
109
|
+
if (input.stillCurrent?.() === false)
|
|
110
|
+
return null;
|
|
111
|
+
input.apiClient.evictContainerWriterProjection?.(input.containerId);
|
|
112
|
+
const second = await shareRemoteContainerOnce({ ...input, previousProjection: undefined }, recipientIdentity.encapsulationPublicKey, resolveProjectionUserKey, false);
|
|
113
|
+
return second === STALE_PATH ? null : second;
|
|
51
114
|
}
|
|
52
115
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,wCAAwC,EAGxC,gCAAgC,GACjC,MAAM,+CAA+C,CAAC;AAGvD,OAAO,EACL,kCAAkC,GAEnC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,mCAAmC,EACnC,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,KAAK,UAAU,8BAA8B,CAC3C,KAAgE;IAEhE,OAAO,wCAAwC,CAAC,GAAG,EAAE,CACnD,mCAAmC,CAAC,KAAK,CAAC,CAC3C,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAgB1C;IAKC,MAAM,wBAAwB,GAAG,gCAAgC,CAC/D,KAAK,CAAC,wBAAwB,EAC9B,wBAAwB,CACzB,CAAC;IACF,MAAM,0BAA0B,GAAG,kCAAkC,CACnE,KAAK,CAAC,0BAA0B,CACjC,CAAC;IACF,MAAM,iBAAiB,GAAG,MAAM,0BAA0B,CACxD,KAAK,CAAC,eAAe,CACtB,CAAC;IACF,IAAI,CAAC,iBAAiB,IAAI,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK,EAAE,CAAC;QAC3D,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,kBAAkB,GAAG,MAAM,sBAAsB,CACrD,KAAK,EACL,wBAAwB,CACzB,CAAC;IACF,IAAI,CAAC,kBAAkB;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,gBAAgB,GAAG,MAAM,8BAA8B,CAAC;QAC5D,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,kBAAkB;QAClB,SAAS,EAAE;YACT,+BAA+B,EAAE,iBAAiB,CAAC,sBAAsB;YACzE,SAAS,EAAE,KAAK,CAAC,eAAe;YAChC,WAAW,EAAE,MAAM;SACpB;QACD,wBAAwB;QACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,+BAA+B,EAAE,KAAK,CAAC,+BAA+B;KACvE,CAAC,CAAC;IACH,IAAI,CAAC,gBAAgB;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,mCAAmC,CAAC;QACzC,sBAAsB,EAAE,KAAK,CAAC,sBAAsB;QACpD,kBAAkB,EAAE,EAAE;QACtB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,YAAY,EAAE,gBAAgB,CAAC,YAAY;QAC3C,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,EAAE,gBAAgB,CAAC,IAAI;QAC3B,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,MAAM,EAAE,GAAG,EAAE,CACX,4BAA4B,CAAC,GAAG,EAAE,CAChC,KAAK,CAAC,SAAS,CAAC,cAAc,CAC5B,KAAK,CAAC,WAAW,EACjB,gBAAgB,CAAC,IAAI,CAAC,OAAO,EAC7B;YACE,qCAAqC,EAAE,KAAK,CAAC,MAAM,CAAC,cAAc;SACnE,CACF,CACF;KACJ,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { ContainerAccessLevel } from \"@tearleads/crypto\";\nimport type {\n ContainerMutationResponse,\n ContainerWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport type {\n ContainerMutationAuthor,\n ContainerShareApi,\n ContainerSharePlan,\n MaterializedContainerSharePlan,\n} from \"../../../../data/containers/shared/types\";\nimport {\n nullOnProjectionVerificationCancellation,\n type ProjectionUserKeyResolver,\n type ReferencedPrincipalPolicyWarmer,\n requireProjectionUserKeyResolver,\n} from \"../../../../data/keyingProjectionVerification\";\nimport type { SecurityIncidentReporter } from \"../../../../data/shared/securityIncidents\";\nimport type { ExecSql } from \"../../../../data/sqlite/sqlSchema\";\nimport {\n requireTrustedUserIdentityResolver,\n type TrustedUserIdentityResolver,\n} from \"../../../../data/trustedUserIdentity\";\nimport {\n submitAcknowledgedContainerMutation,\n submitPlainContainerMutation,\n} from \"../mutationSubmit\";\nimport { buildMaterializedContainerSharePlan } from \"./shareMaterialization\";\nimport { resolveShareProjection } from \"./shareProjection\";\n\nasync function buildCurrentContainerSharePlan(\n input: Parameters<typeof buildMaterializedContainerSharePlan>[0],\n): Promise<MaterializedContainerSharePlan | null> {\n return nullOnProjectionVerificationCancellation(() =>\n buildMaterializedContainerSharePlan(input),\n );\n}\n\nexport async function shareRemoteContainer(input: {\n reportSecurityIncident: SecurityIncidentReporter;\n accessLevel: ContainerAccessLevel;\n apiClient: ContainerShareApi;\n author: ContainerMutationAuthor;\n containerId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n previousProjection?: ContainerWriterProjectionResponse | undefined;\n recipientUserId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n signedAt?: string | undefined;\n stillCurrent?: (() => boolean) | undefined;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n}): Promise<{\n containerKey: Uint8Array;\n plan: ContainerSharePlan;\n response: ContainerMutationResponse;\n} | null> {\n const resolveProjectionUserKey = requireProjectionUserKeyResolver(\n input.resolveProjectionUserKey,\n \"Remote container share\",\n );\n const resolveTrustedUserIdentity = requireTrustedUserIdentityResolver(\n input.resolveTrustedUserIdentity,\n );\n const recipientIdentity = await resolveTrustedUserIdentity(\n input.recipientUserId,\n );\n if (!recipientIdentity || input.stillCurrent?.() === false) {\n return null;\n }\n const previousProjection = await resolveShareProjection(\n input,\n resolveProjectionUserKey,\n );\n if (!previousProjection) return null;\n\n const materializedPlan = await buildCurrentContainerSharePlan({\n accessLevel: input.accessLevel,\n author: input.author,\n eventId: input.eventId,\n execSql: input.execSql,\n previousProjection,\n recipient: {\n recipientEncapsulationPublicKey: recipientIdentity.encapsulationPublicKey,\n subjectId: input.recipientUserId,\n subjectType: \"user\",\n },\n resolveProjectionUserKey,\n signedAt: input.signedAt,\n stillCurrent: input.stillCurrent,\n targetSecretKey: input.targetSecretKey,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n if (!materializedPlan) return null;\n return submitAcknowledgedContainerMutation({\n reportSecurityIncident: input.reportSecurityIncident,\n recitationPolicies: [],\n apiClient: input.apiClient,\n author: input.author,\n containerKey: materializedPlan.containerKey,\n execSql: input.execSql,\n plan: materializedPlan.plan,\n stillCurrent: input.stillCurrent,\n submit: () =>\n submitPlainContainerMutation(() =>\n input.apiClient.shareContainer(\n input.containerId,\n materializedPlan.plan.request,\n {\n expectedPaymentRequiredOrganizationId: input.author.organizationId,\n },\n ),\n ),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type ContainerAccessLevel,\n KeyingVerificationError,\n} from \"@tearleads/crypto\";\nimport type { ContainerMutationRequest } from \"@tearleads/validators/request\";\nimport type {\n ContainerMutationResponse,\n ContainerWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { isStaleParentContainerPathFailure } from \"../../../../data/containers/shared/mutationFailures\";\nimport type {\n ContainerMutationAuthor,\n ContainerRotationResult,\n ContainerShareApi,\n ContainerSharePlan,\n MaterializedContainerSharePlan,\n} from \"../../../../data/containers/shared/types\";\nimport {\n nullOnProjectionVerificationCancellation,\n type ProjectionUserKeyResolver,\n type ReferencedPrincipalPolicyWarmer,\n requireProjectionUserKeyResolver,\n} from \"../../../../data/keyingProjectionVerification\";\nimport type { SecurityIncidentReporter } from \"../../../../data/shared/securityIncidents\";\nimport type { ExecSql } from \"../../../../data/sqlite/sqlSchema\";\nimport {\n requireTrustedUserIdentityResolver,\n type TrustedUserIdentityResolver,\n} from \"../../../../data/trustedUserIdentity\";\nimport {\n submitAcknowledgedContainerMutation,\n submitPlainContainerMutation,\n} from \"../mutationSubmit\";\nimport { buildMaterializedContainerSharePlan } from \"./shareMaterialization\";\nimport { resolveShareProjection } from \"./shareProjection\";\n\nasync function buildCurrentContainerSharePlan(\n input: Parameters<typeof buildMaterializedContainerSharePlan>[0],\n): Promise<MaterializedContainerSharePlan | null> {\n return nullOnProjectionVerificationCancellation(() =>\n buildMaterializedContainerSharePlan(input),\n );\n}\n\ntype ShareRemoteContainerInput = {\n reportSecurityIncident: SecurityIncidentReporter;\n accessLevel: ContainerAccessLevel;\n apiClient: ContainerShareApi;\n author: ContainerMutationAuthor;\n containerId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n previousProjection?: ContainerWriterProjectionResponse | undefined;\n recipientUserId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n signedAt?: string | undefined;\n stillCurrent?: (() => boolean) | undefined;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n};\n\ntype SharedRemoteContainer = {\n containerKey: Uint8Array;\n plan: ContainerSharePlan;\n response: ContainerMutationResponse;\n};\n\n/** A path a head advanced under, which a share plans once more (#2647). */\nconst STALE_PATH = Symbol(\"stale-path\");\n\n/**\n * Submit through the status-bearing endpoint when the adapter has one. The\n * refusal that ends the share is reported once submission settles, as the\n * plain endpoint's would be; a stale path the share plans again is not.\n */\nasync function submitShare(\n input: ShareRemoteContainerInput,\n request: ContainerMutationRequest,\n retriesStalePath: boolean,\n): Promise<ContainerRotationResult> {\n const options = {\n expectedPaymentRequiredOrganizationId: input.author.organizationId,\n };\n const { shareContainerResult } = input.apiClient;\n if (!shareContainerResult) {\n return submitPlainContainerMutation(() =>\n input.apiClient.shareContainer(input.containerId, request, options),\n );\n }\n const result = await shareContainerResult.call(\n input.apiClient,\n input.containerId,\n request,\n { ...options, reportErrors: false },\n );\n if (result.ok) return { ok: true, data: result.data };\n return retriesStalePath && isStaleParentContainerPathFailure(result)\n ? { ok: false, code: result.code, status: result.status }\n : result;\n}\n\nfunction planShare(\n input: ShareRemoteContainerInput,\n recipientEncapsulationPublicKey: Uint8Array,\n resolveProjectionUserKey: ProjectionUserKeyResolver,\n) {\n return resolveShareProjection(input, resolveProjectionUserKey).then(\n (previousProjection) =>\n previousProjection &&\n buildCurrentContainerSharePlan({\n accessLevel: input.accessLevel,\n author: input.author,\n eventId: input.eventId,\n execSql: input.execSql,\n previousProjection,\n recipient: {\n recipientEncapsulationPublicKey,\n subjectId: input.recipientUserId,\n subjectType: \"user\",\n },\n resolveProjectionUserKey,\n signedAt: input.signedAt,\n stillCurrent: input.stillCurrent,\n targetSecretKey: input.targetSecretKey,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n }),\n );\n}\n\nasync function shareRemoteContainerOnce(\n input: ShareRemoteContainerInput,\n recipientEncapsulationPublicKey: Uint8Array,\n resolveProjectionUserKey: ProjectionUserKeyResolver,\n retriesStalePath: boolean,\n): Promise<SharedRemoteContainer | typeof STALE_PATH | null> {\n let materializedPlan: Awaited<ReturnType<typeof planShare>>;\n try {\n materializedPlan = await planShare(\n input,\n recipientEncapsulationPublicKey,\n resolveProjectionUserKey,\n );\n } catch (error) {\n // The path the share was handed predates a pin this device has since\n // advanced, most often through its own re-citation. A fresh projection\n // is held to the same pins, so a server that rolls a head back still\n // fails the share, on its second plan.\n if (\n retriesStalePath &&\n error instanceof KeyingVerificationError &&\n error.code === \"rollback\"\n )\n return STALE_PATH;\n throw error;\n }\n if (!materializedPlan) return null;\n let stalePath = false;\n const shared = await submitAcknowledgedContainerMutation({\n reportSecurityIncident: input.reportSecurityIncident,\n recitationPolicies: [],\n apiClient: input.apiClient,\n author: input.author,\n containerKey: materializedPlan.containerKey,\n execSql: input.execSql,\n plan: materializedPlan.plan,\n stillCurrent: input.stillCurrent,\n submit: async () => {\n const result = await submitShare(\n input,\n materializedPlan.plan.request,\n retriesStalePath,\n );\n stalePath =\n !result.ok &&\n retriesStalePath &&\n isStaleParentContainerPathFailure(result);\n return result;\n },\n });\n return shared ?? (stalePath ? STALE_PATH : null);\n}\n\n/**\n * Share a container with a user. A head on the signed path can advance while\n * the share is in flight, most often under this device's own background\n * re-citation of held descendants after a share above it. The API refuses\n * that as a stale path, and the share is planned once more from a fresh\n * projection, as a create under a stale parent is.\n */\nexport async function shareRemoteContainer(\n input: ShareRemoteContainerInput,\n): Promise<SharedRemoteContainer | null> {\n const resolveProjectionUserKey = requireProjectionUserKeyResolver(\n input.resolveProjectionUserKey,\n \"Remote container share\",\n );\n const resolveTrustedUserIdentity = requireTrustedUserIdentityResolver(\n input.resolveTrustedUserIdentity,\n );\n const recipientIdentity = await resolveTrustedUserIdentity(\n input.recipientUserId,\n );\n if (!recipientIdentity || input.stillCurrent?.() === false) {\n return null;\n }\n const first = await shareRemoteContainerOnce(\n input,\n recipientIdentity.encapsulationPublicKey,\n resolveProjectionUserKey,\n true,\n );\n if (first !== STALE_PATH) return first;\n if (input.stillCurrent?.() === false) return null;\n input.apiClient.evictContainerWriterProjection?.(input.containerId);\n const second = await shareRemoteContainerOnce(\n { ...input, previousProjection: undefined },\n recipientIdentity.encapsulationPublicKey,\n resolveProjectionUserKey,\n false,\n );\n return second === STALE_PATH ? null : second;\n}\n"]}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { MAX_ROTATION_CONTAINER_REKEYS } from "../../../internal/validators/util/index.js";
|
|
2
1
|
import { rememberVerifiedContainerHeads } from "../../../data/containers/shared/heldContainerHeads.js";
|
|
3
2
|
import { acknowledgeContainerMutationBatch } from "../../../data/containers/shared/mutationAcknowledgement.js";
|
|
4
3
|
import { getTargetContainerContext, readContainerState, } from "../../../data/containers/shared/projection.js";
|
|
@@ -12,7 +11,7 @@ import { buildMaterializedContainerRevokePlan } from "../child/revoke.js";
|
|
|
12
11
|
import { selectReplacementPrincipalPolicyReferences } from "../child/rotation/replacementPrincipalPolicyReferences.js";
|
|
13
12
|
import { buildMaterializedContainerSharePlan } from "../child/share/shareMaterialization.js";
|
|
14
13
|
import { withPrecedingRematerialization } from "./precedingPrincipalRematerialization.js";
|
|
15
|
-
import { addPlan, carryLevel, rerootOnAuthoredPaths, staleLevelsAbove, } from "./principalContainerRematerializationPlanning.js";
|
|
14
|
+
import { addPlan, assertNamedContainersRotated, carryLevel, planOnceMoreAfterRollback, rerootOnAuthoredPaths, staleLevelsAbove, } from "./principalContainerRematerializationPlanning.js";
|
|
16
15
|
import { loadRematerializationTargets, loadServedProjection, seededPrincipalPolicyCache, } from "./principalContainerRematerializationTargets.js";
|
|
17
16
|
function matchingGroupGrant(input) {
|
|
18
17
|
return (input.directGrants.find((grant) => grant.subjectType === "group" && grant.subjectId === input.groupId) ?? null);
|
|
@@ -186,7 +185,10 @@ async function planRematerializationTarget(batch, target) {
|
|
|
186
185
|
* rotation it rides, and a named container the batch rematerializes anyway is
|
|
187
186
|
* re-planned there rather than rotated twice.
|
|
188
187
|
*/
|
|
189
|
-
|
|
188
|
+
function buildPrincipalContainerRematerializationPlans(input, carriedContainerIds = []) {
|
|
189
|
+
return planOnceMoreAfterRollback(input.apiClient, [...input.grants.map((grant) => grant.containerId), ...carriedContainerIds], () => planRematerializationBatch(input, carriedContainerIds));
|
|
190
|
+
}
|
|
191
|
+
async function planRematerializationBatch(input, carriedContainerIds) {
|
|
190
192
|
input = withPrecedingRematerialization(input);
|
|
191
193
|
if (input.revokedContainerId &&
|
|
192
194
|
!input.grants.some((grant) => grant.containerId === input.revokedContainerId)) {
|
|
@@ -231,22 +233,6 @@ async function buildPrincipalContainerRematerializationPlans(input, carriedConta
|
|
|
231
233
|
},
|
|
232
234
|
};
|
|
233
235
|
}
|
|
234
|
-
/**
|
|
235
|
-
* The server names a level it found pinned to a retired epoch. A batch answers
|
|
236
|
-
* with a rotation of it: a carried rekey, or its own rematerialization when
|
|
237
|
-
* that is a rekey or revoke. A rematerialized grant rotates nothing, so a
|
|
238
|
-
* batch that both grants a container and must re-key it cannot be signed;
|
|
239
|
-
* no policy change the SDK builds combines the two.
|
|
240
|
-
*/
|
|
241
|
-
function assertNamedContainersRotated(plans, requiredContainerIds) {
|
|
242
|
-
const rotatedIds = new Set(plans.flatMap((entry) => entry.rotated ? [entry.planned.plan.containerId] : []));
|
|
243
|
-
const unrotated = requiredContainerIds
|
|
244
|
-
.slice(0, MAX_ROTATION_CONTAINER_REKEYS)
|
|
245
|
-
.find((containerId) => !rotatedIds.has(containerId));
|
|
246
|
-
if (unrotated !== undefined) {
|
|
247
|
-
throw new Error(`Container ${unrotated} needs a rekey this policy change only grants`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
236
|
export async function preparePrincipalContainerRematerializationBatch(input) {
|
|
251
237
|
let { entries, retiredContainerIds, preceding } = await buildPrincipalContainerRematerializationPlans(input);
|
|
252
238
|
const requests = () => entries.map((entry) => entry.planned.plan.request);
|