@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.
Files changed (33) hide show
  1. package/client/containerContents/documentDiscovery.js +2 -2
  2. package/client/containerContents/documentDiscovery.js.map +1 -1
  3. package/data/containers/shared/mutationFailures.js.map +1 -1
  4. package/data/containers/shared/types.d.ts +3 -0
  5. package/data/containers/shared/types.js.map +1 -1
  6. package/data/persistence/container-contents/containerAccessDiscovery.js +28 -0
  7. package/data/persistence/container-contents/containerAccessDiscovery.js.map +1 -0
  8. package/data/persistence/container-contents/containerMetadataMutationPersistence.js +22 -17
  9. package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
  10. package/data/persistence/documents/documentDiscoveryEvidencePersistence.d.ts +1 -0
  11. package/data/persistence/documents/documentDiscoveryEvidencePersistence.js +1 -0
  12. package/data/persistence/documents/documentDiscoveryEvidencePersistence.js.map +1 -1
  13. package/internal/api-client/ApiClient.d.ts +2 -0
  14. package/internal/api-client/ApiClient.js +13 -11
  15. package/internal/api-client/ApiClient.js.map +1 -1
  16. package/package.json +1 -1
  17. package/workflows/container-contents/documentDiscovery/documentDiscoveryApply.js +6 -3
  18. package/workflows/container-contents/documentDiscovery/documentDiscoveryApply.js.map +1 -1
  19. package/workflows/container-contents/documentDiscovery/documentDiscoveryTypes.d.ts +4 -3
  20. package/workflows/container-contents/documentDiscovery/documentDiscoveryTypes.js.map +1 -1
  21. package/workflows/container-contents/documentDiscovery/index.js +15 -9
  22. package/workflows/container-contents/documentDiscovery/index.js.map +1 -1
  23. package/workflows/container-contents/syncPause/discoveryGuard.js +12 -11
  24. package/workflows/container-contents/syncPause/discoveryGuard.js.map +1 -1
  25. package/workflows/containers/child/share/index.d.ts +14 -4
  26. package/workflows/containers/child/share/index.js +93 -30
  27. package/workflows/containers/child/share/index.js.map +1 -1
  28. package/workflows/containers/rematerialization/principalContainerRematerialization.js +5 -19
  29. package/workflows/containers/rematerialization/principalContainerRematerialization.js.map +1 -1
  30. package/workflows/containers/rematerialization/principalContainerRematerializationPlanning.js +38 -0
  31. package/workflows/containers/rematerialization/principalContainerRematerializationPlanning.js.map +1 -1
  32. package/workflows/organizations/groupShare/sharePrincipalPolicy.d.ts +1 -0
  33. 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
- replaceDocumentLinksBatch: (inputs: ReadonlyArray<DocumentLinkInput>) => Promise<void>;
135
- saveContainerDocumentWatermark: (containerId: string, watermark: SyncWatermark) => Promise<void>;
136
- upsertDiscoveredDocuments: (inputs: ReadonlyArray<DiscoveredDocumentInput>) => Promise<ReadonlyArray<DocumentSummary>>;
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 Promise.all(listedDocumentsByContainer.map(({ containerId, listedDocuments }) => listedDocuments
154
- ? saveAppliedContainerDocumentWatermark({
155
- containerId,
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 container's organization staying
7
- * unpaused. A listing still in flight when the organization is paused stops
8
- * being current, so the apply stops at its next check, and each write it has
9
- * already started re-checks under the SQLite mutation lock the pause cleanup
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 guardDiscoveryAgainstPause(input) {
15
- const unlessPaused = (skipped, write) => runSerializedSqlMutation(input.execSql, async (lockedExecSql) => input.isPaused() ? skipped : write(lockedExecSql));
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) => unlessPaused(undefined, (lockedExecSql) => sqlDocumentContainerProjectionPersistence.replaceDocumentLinksBatch(lockedExecSql, inputs)),
18
- saveContainerDocumentWatermark: (containerId, watermark) => unlessPaused(undefined, async (lockedExecSql) => {
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) => unlessPaused([], (lockedExecSql) => upsertDiscoveredDocuments(lockedExecSql, 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;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAI1C;IACC,MAAM,YAAY,GAAG,CACnB,OAAU,EACV,KAA6C,EACjC,EAAE,CACd,wBAAwB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,CAC9D,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAClD,CAAC;IACJ,OAAO;QACL,yBAAyB,EAAE,CAAC,MAAM,EAAE,EAAE,CACpC,YAAY,CAAC,SAAS,EAAE,CAAC,aAAa,EAAE,EAAE,CACxC,yCAAyC,CAAC,yBAAyB,CACjE,aAAa,EACb,MAAM,CACP,CACF;QACH,8BAA8B,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,EAAE,CACzD,YAAY,CAAC,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE;YAC9C,MAAM,oCAAoC,CAAC,aAAa,CACtD,aAAa,EACb,yBAAyB,CAAC,WAAW,CAAC,EACtC,SAAS,CACV,CAAC;QACJ,CAAC,CAAC;QACJ,yBAAyB,EAAE,CAAC,MAAM,EAAE,EAAE,CACpC,YAAY,CAAC,EAAE,EAAE,CAAC,aAAa,EAAE,EAAE,CACjC,yBAAyB,CAAC,aAAa,EAAE,MAAM,CAAC,CACjD;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 container's organization staying\n * unpaused. A listing still in flight when the organization is paused stops\n * being current, so the apply stops at its next check, and each write it has\n * already started re-checks under the SQLite mutation lock the pause cleanup\n * holds. 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 guardDiscoveryAgainstPause(input: {\n readonly execSql: ExecSql;\n readonly isPaused: () => boolean;\n readonly verifyDiscoveredDocuments: DiscoverContainerDocumentsOptions[\"verifyDiscoveredDocuments\"];\n}): PauseGuardedDiscovery {\n const unlessPaused = <T>(\n skipped: T,\n write: (lockedExecSql: ExecSql) => Promise<T>,\n ): Promise<T> =>\n runSerializedSqlMutation(input.execSql, async (lockedExecSql) =>\n input.isPaused() ? skipped : write(lockedExecSql),\n );\n return {\n replaceDocumentLinksBatch: (inputs) =>\n unlessPaused(undefined, (lockedExecSql) =>\n sqlDocumentContainerProjectionPersistence.replaceDocumentLinksBatch(\n lockedExecSql,\n inputs,\n ),\n ),\n saveContainerDocumentWatermark: (containerId, watermark) =>\n unlessPaused(undefined, async (lockedExecSql) => {\n await sqlContainerSyncWatermarkPersistence.saveWatermark(\n lockedExecSql,\n containerContentsSyncLane(containerId),\n watermark,\n );\n }),\n upsertDiscoveredDocuments: (inputs) =>\n unlessPaused([], (lockedExecSql) =>\n upsertDiscoveredDocuments(lockedExecSql, inputs),\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
+ {"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 { ContainerAccessLevel } from "../../../../internal/crypto/index.js";
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
- export declare function shareRemoteContainer(input: {
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
- }): Promise<{
24
+ };
25
+ type SharedRemoteContainer = {
25
26
  containerKey: Uint8Array;
26
27
  plan: ContainerSharePlan;
27
28
  response: ContainerMutationResponse;
28
- } | null>;
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
- export async function shareRemoteContainer(input) {
10
- const resolveProjectionUserKey = requireProjectionUserKeyResolver(input.resolveProjectionUserKey, "Remote container share");
11
- const resolveTrustedUserIdentity = requireTrustedUserIdentityResolver(input.resolveTrustedUserIdentity);
12
- const recipientIdentity = await resolveTrustedUserIdentity(input.recipientUserId);
13
- if (!recipientIdentity || input.stillCurrent?.() === false) {
14
- return null;
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
- return submitAcknowledgedContainerMutation({
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: () => submitPlainContainerMutation(() => input.apiClient.shareContainer(input.containerId, materializedPlan.plan.request, {
48
- expectedPaymentRequiredOrganizationId: input.author.organizationId,
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
- async function buildPrincipalContainerRematerializationPlans(input, carriedContainerIds = []) {
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);