@tearleads/client-sdk 0.1.101 → 0.1.103

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 (32) hide show
  1. package/client/containerContents/documentDiscovery.js +2 -2
  2. package/client/containerContents/documentDiscovery.js.map +1 -1
  3. package/data/containers/shared/types.d.ts +2 -0
  4. package/data/containers/shared/types.js.map +1 -1
  5. package/data/persistence/container-contents/containerAccessDiscovery.js +28 -0
  6. package/data/persistence/container-contents/containerAccessDiscovery.js.map +1 -0
  7. package/data/persistence/container-contents/containerMetadataMutationPersistence.js +22 -17
  8. package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
  9. package/data/persistence/documents/documentDiscoveryEvidencePersistence.d.ts +1 -0
  10. package/data/persistence/documents/documentDiscoveryEvidencePersistence.js +1 -0
  11. package/data/persistence/documents/documentDiscoveryEvidencePersistence.js.map +1 -1
  12. package/package.json +1 -1
  13. package/stores/container-contents/remoteEvents/remoteEventProjectionInvalidation.js +3 -33
  14. package/stores/container-contents/remoteEvents/remoteEventProjectionInvalidation.js.map +1 -1
  15. package/workflows/container-contents/container-state/projectionCache.js +34 -0
  16. package/workflows/container-contents/container-state/projectionCache.js.map +1 -1
  17. package/workflows/container-contents/container-state/types.js.map +1 -1
  18. package/workflows/container-contents/documentDiscovery/documentDiscoveryApply.js +6 -3
  19. package/workflows/container-contents/documentDiscovery/documentDiscoveryApply.js.map +1 -1
  20. package/workflows/container-contents/documentDiscovery/documentDiscoveryTypes.d.ts +4 -3
  21. package/workflows/container-contents/documentDiscovery/documentDiscoveryTypes.js.map +1 -1
  22. package/workflows/container-contents/documentDiscovery/index.js +15 -9
  23. package/workflows/container-contents/documentDiscovery/index.js.map +1 -1
  24. package/workflows/container-contents/remoteHydration/remoteContainers/remoteContainerState.js +17 -0
  25. package/workflows/container-contents/remoteHydration/remoteContainers/remoteContainerState.js.map +1 -1
  26. package/workflows/container-contents/remoteHydration/types.d.ts +2 -1
  27. package/workflows/container-contents/remoteHydration/types.js.map +1 -1
  28. package/workflows/container-contents/syncPause/discoveryGuard.js +12 -11
  29. package/workflows/container-contents/syncPause/discoveryGuard.js.map +1 -1
  30. package/workflows/containers/child/revoke.d.ts +7 -3
  31. package/workflows/containers/child/revoke.js +73 -17
  32. package/workflows/containers/child/revoke.js.map +1 -1
@@ -68,4 +68,38 @@ export async function loadContainerWriterProjectionForState(input) {
68
68
  }
69
69
  return projection;
70
70
  }
71
+ /**
72
+ * Drop every cached writer projection a hint or listing invalidates before hydration
73
+ * lands, so the next share or move fetches a fresh one instead of submitting
74
+ * against a stale manifest and conflicting. Descendants cite the same ancestor
75
+ * manifests in their paths, so the locally known subtree under each invalidated
76
+ * container is dropped too (an unhydrated subtree has nothing cached). Both
77
+ * layers go: the in-memory copies on each container state, and the api-client's
78
+ * cached writer projections that cite an invalidated container. Document
79
+ * projections cite their paths too, so they are found by citation rather than
80
+ * through the links, which live in SQLite; projections citing none of them stay
81
+ * cached (#2395). Each open document store drops its own in-memory copy on the
82
+ * same realtime hint; explicit document pulls obtain fresh projections.
83
+ */
84
+ export function invalidateContainerProjectionCaches(state, invalidated) {
85
+ if (invalidated.length === 0)
86
+ return;
87
+ const roots = new Set(invalidated);
88
+ const stale = new Set(invalidated);
89
+ for (const [id, containerState] of state.containersById) {
90
+ let parentId = containerState.container.parentId;
91
+ for (let depth = 0; parentId !== null &&
92
+ !roots.has(parentId) &&
93
+ depth < state.containersById.size; depth++)
94
+ parentId = state.containersById.get(parentId)?.container.parentId ?? null;
95
+ if (parentId !== null)
96
+ stale.add(id);
97
+ }
98
+ for (const id of stale) {
99
+ const containerState = state.containersById.get(id);
100
+ if (containerState)
101
+ invalidateContainerProjections(containerState);
102
+ }
103
+ state.runtime.apiClient.evictWriterProjectionsCiting(invalidated);
104
+ }
71
105
  //# sourceMappingURL=projectionCache.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"projectionCache.js","sourceRoot":"","sources":["projectionCache.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContainerWriterProjectionResponse } from \"@tearleads/validators/response\";\nimport { projectionGeneration } from \"../projectionGeneration\";\nimport type { ContainerState } from \"../remoteHydration\";\nimport type { ContainerWorkflowRuntime } from \"./types\";\n\nfunction cachedProjectionMatchesContainerState(input: {\n containerState: ContainerState;\n projection: ContainerWriterProjectionResponse;\n}): boolean {\n const { containerState, projection } = input;\n if (projection.containerId !== containerState.container.id) {\n return false;\n }\n\n const targetManifest = projection.path.at(-1);\n const targetKek = projection.containerKeks.at(-1);\n if (\n !targetManifest ||\n !targetKek ||\n targetKek.containerId !== projection.containerId ||\n targetKek.accessManifestHash !== targetManifest.manifestHash\n ) {\n return false;\n }\n\n const expectedAccessStateHash = containerState.record.accessStateHash;\n return (\n typeof expectedAccessStateHash !== \"string\" ||\n expectedAccessStateHash.length === 0 ||\n targetManifest.manifestHash === expectedAccessStateHash\n );\n}\n\nexport function getCachedContainerWriterProjection(\n containerState: ContainerState,\n): ContainerWriterProjectionResponse | null {\n const projection = containerState.containerWriterProjection ?? null;\n return projection &&\n cachedProjectionMatchesContainerState({ containerState, projection })\n ? projection\n : null;\n}\n\n// A hint can invalidate a container while its load is in flight; each retry\n// re-reads the generation, so this only bounds a pathological hint storm.\nconst MAX_PROJECTION_LOAD_ATTEMPTS = 3;\n\n/**\n * Drop both cached writer projections (the container's and its metadata\n * document's) and advance their shared generation, so an operation that\n * started before this invalidation (and may carry a pre-hint manifest) cannot\n * install its result.\n */\nexport function invalidateContainerProjections(\n containerState: ContainerState,\n): void {\n containerState.containerWriterProjection = null;\n containerState.metadataWriterProjection = null;\n containerState.writerProjectionGeneration =\n projectionGeneration(containerState) + 1;\n}\n\n/**\n * Install a container projection an operation fetched or was handed back,\n * unless a hint invalidated the container while that operation ran: its\n * answer may predate the hint, so the slot is left as the hint left it and the\n * next operation fetches fresh.\n */\nexport function installContainerWriterProjection(\n containerState: ContainerState,\n projection: ContainerWriterProjectionResponse | null,\n capturedGeneration: number,\n): void {\n if (projectionGeneration(containerState) !== capturedGeneration) return;\n containerState.containerWriterProjection = projection;\n}\n\nexport async function loadContainerWriterProjectionForState(input: {\n containerState: ContainerState;\n runtime: ContainerWorkflowRuntime;\n}): Promise<ContainerWriterProjectionResponse | null> {\n const cachedProjection = getCachedContainerWriterProjection(\n input.containerState,\n );\n if (cachedProjection) {\n return cachedProjection;\n }\n\n let projection: ContainerWriterProjectionResponse | null = null;\n for (let attempt = 0; attempt < MAX_PROJECTION_LOAD_ATTEMPTS; attempt++) {\n const generation = projectionGeneration(input.containerState);\n projection = await input.runtime.apiClient.getContainerWriterProjection(\n input.containerState.container.id,\n );\n if (projectionGeneration(input.containerState) === generation) {\n installContainerWriterProjection(\n input.containerState,\n projection,\n generation,\n );\n return projection;\n }\n // Invalidated while in flight (the api-client entry went with it): this\n // answer may predate the hint, so fetch again instead of caching it.\n }\n return projection;\n}\n"]}
1
+ {"version":3,"file":"projectionCache.js","sourceRoot":"","sources":["projectionCache.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContainerWriterProjectionResponse } from \"@tearleads/validators/response\";\nimport { projectionGeneration } from \"../projectionGeneration\";\nimport type {\n ContainerState,\n RemoteContainerHydrationState,\n} from \"../remoteHydration/types\";\nimport type { ContainerWorkflowRuntime } from \"./types\";\n\nfunction cachedProjectionMatchesContainerState(input: {\n containerState: ContainerState;\n projection: ContainerWriterProjectionResponse;\n}): boolean {\n const { containerState, projection } = input;\n if (projection.containerId !== containerState.container.id) {\n return false;\n }\n\n const targetManifest = projection.path.at(-1);\n const targetKek = projection.containerKeks.at(-1);\n if (\n !targetManifest ||\n !targetKek ||\n targetKek.containerId !== projection.containerId ||\n targetKek.accessManifestHash !== targetManifest.manifestHash\n ) {\n return false;\n }\n\n const expectedAccessStateHash = containerState.record.accessStateHash;\n return (\n typeof expectedAccessStateHash !== \"string\" ||\n expectedAccessStateHash.length === 0 ||\n targetManifest.manifestHash === expectedAccessStateHash\n );\n}\n\nexport function getCachedContainerWriterProjection(\n containerState: ContainerState,\n): ContainerWriterProjectionResponse | null {\n const projection = containerState.containerWriterProjection ?? null;\n return projection &&\n cachedProjectionMatchesContainerState({ containerState, projection })\n ? projection\n : null;\n}\n\n// A hint can invalidate a container while its load is in flight; each retry\n// re-reads the generation, so this only bounds a pathological hint storm.\nconst MAX_PROJECTION_LOAD_ATTEMPTS = 3;\n\n/**\n * Drop both cached writer projections (the container's and its metadata\n * document's) and advance their shared generation, so an operation that\n * started before this invalidation (and may carry a pre-hint manifest) cannot\n * install its result.\n */\nexport function invalidateContainerProjections(\n containerState: ContainerState,\n): void {\n containerState.containerWriterProjection = null;\n containerState.metadataWriterProjection = null;\n containerState.writerProjectionGeneration =\n projectionGeneration(containerState) + 1;\n}\n\n/**\n * Install a container projection an operation fetched or was handed back,\n * unless a hint invalidated the container while that operation ran: its\n * answer may predate the hint, so the slot is left as the hint left it and the\n * next operation fetches fresh.\n */\nexport function installContainerWriterProjection(\n containerState: ContainerState,\n projection: ContainerWriterProjectionResponse | null,\n capturedGeneration: number,\n): void {\n if (projectionGeneration(containerState) !== capturedGeneration) return;\n containerState.containerWriterProjection = projection;\n}\n\nexport async function loadContainerWriterProjectionForState(input: {\n containerState: ContainerState;\n runtime: ContainerWorkflowRuntime;\n}): Promise<ContainerWriterProjectionResponse | null> {\n const cachedProjection = getCachedContainerWriterProjection(\n input.containerState,\n );\n if (cachedProjection) {\n return cachedProjection;\n }\n\n let projection: ContainerWriterProjectionResponse | null = null;\n for (let attempt = 0; attempt < MAX_PROJECTION_LOAD_ATTEMPTS; attempt++) {\n const generation = projectionGeneration(input.containerState);\n projection = await input.runtime.apiClient.getContainerWriterProjection(\n input.containerState.container.id,\n );\n if (projectionGeneration(input.containerState) === generation) {\n installContainerWriterProjection(\n input.containerState,\n projection,\n generation,\n );\n return projection;\n }\n // Invalidated while in flight (the api-client entry went with it): this\n // answer may predate the hint, so fetch again instead of caching it.\n }\n return projection;\n}\n\n/**\n * Drop every cached writer projection a hint or listing invalidates before hydration\n * lands, so the next share or move fetches a fresh one instead of submitting\n * against a stale manifest and conflicting. Descendants cite the same ancestor\n * manifests in their paths, so the locally known subtree under each invalidated\n * container is dropped too (an unhydrated subtree has nothing cached). Both\n * layers go: the in-memory copies on each container state, and the api-client's\n * cached writer projections that cite an invalidated container. Document\n * projections cite their paths too, so they are found by citation rather than\n * through the links, which live in SQLite; projections citing none of them stay\n * cached (#2395). Each open document store drops its own in-memory copy on the\n * same realtime hint; explicit document pulls obtain fresh projections.\n */\nexport function invalidateContainerProjectionCaches(\n state: Pick<RemoteContainerHydrationState, \"containersById\" | \"runtime\">,\n invalidated: readonly string[],\n): void {\n if (invalidated.length === 0) return;\n const roots = new Set(invalidated);\n const stale = new Set(invalidated);\n for (const [id, containerState] of state.containersById) {\n let parentId = containerState.container.parentId;\n for (\n let depth = 0;\n parentId !== null &&\n !roots.has(parentId) &&\n depth < state.containersById.size;\n depth++\n )\n parentId = state.containersById.get(parentId)?.container.parentId ?? null;\n if (parentId !== null) stale.add(id);\n }\n for (const id of stale) {\n const containerState = state.containersById.get(id);\n if (containerState) invalidateContainerProjections(containerState);\n }\n state.runtime.apiClient.evictWriterProjectionsCiting(invalidated);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ContainerSystemSlot } from \"@tearleads/validators/containerSystemSlot\";\nimport type {\n ContainerDeleteResponse,\n ContainerWriterProjectionResponse,\n ReferencedPrincipalStateResponse,\n} from \"@tearleads/validators/response\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport type { DocumentRecord } from \"../../../data/persistence/documents/records\";\nimport type {\n createRemoteContainer,\n moveRemoteContainer,\n shareRemoteContainer,\n} from \"../../containers\";\nimport type { createRemoteDocument } from \"../../documents\";\nimport type { shareRemoteContainerWithGroup } from \"../../organizations/groupShare\";\nimport type { CurrentPrincipalPolicyReader } from \"../../principals/currentPrincipalPolicyReader\";\nimport type { PrincipalPolicyHistoryReader } from \"../../principals/principalPolicyHistoryReader\";\nimport type {\n ContainerContentsPersistence,\n ContainerCreateIntentRecord,\n ContainerMoveIntentRecord,\n} from \"../containerPersistence\";\nimport type { createInitializedContainerMetadataDocument } from \"../metadata/containerMetadataDocument\";\nimport type {\n ContainerState,\n RemoteContainer as HydratedRemoteContainer,\n RemoteContainerHydrationHost,\n} from \"../remoteHydration\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\n\nexport type ContainerWorkflowApi = Parameters<\n typeof createRemoteContainer\n>[0][\"apiClient\"] &\n Parameters<typeof shareRemoteContainer>[0][\"apiClient\"] &\n Parameters<typeof shareRemoteContainerWithGroup>[0][\"apiClient\"] &\n Parameters<typeof moveRemoteContainer>[0][\"apiClient\"] &\n Parameters<typeof createRemoteDocument>[0][\"apiClient\"] &\n Partial<PrincipalPolicyHistoryReader> &\n CurrentPrincipalPolicyReader & {\n deleteContainerResult(\n containerId: string,\n options?: {\n expectedPaymentRequiredOrganizationId?: string;\n reportErrors?: boolean;\n requiredAncestorId?: string | undefined;\n },\n ): Promise<\n | { data: ContainerDeleteResponse; ok: true }\n | {\n ok: false;\n report: () => void;\n status: number | null;\n }\n >;\n primeContainerWriterProjection(\n containerId: string,\n projection: ContainerWriterProjectionResponse,\n ): void;\n };\n\nexport interface ContainerWorkflowRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n readonly apiClient: ContainerWorkflowApi;\n}\n\nexport interface CreatedRemoteContainerState {\n accessManifestHash: string;\n /**\n * The metadata seed the create committed, settled by the caller instead of\n * a first metadata sync. Absent when the create carried no seed or fell back\n * to a plain create.\n */\n metadataSeed?:\n | { readonly commitLsn: string | null; readonly updateId: string }\n | undefined;\n /** The slot this client signed into the create; never the server's echo. */\n systemSlot: ContainerSystemSlot | null;\n containerId: string;\n createdAt: string;\n metadataDocumentId: string;\n organizationId: string;\n /** A created container is a child; the acknowledgement echoed this parent. */\n parentId: string;\n persistedMetadataState: Pick<\n DocumentRecord,\n | \"documentId\"\n | \"contentKeyBundle\"\n | \"documentKekTargets\"\n | \"documentManifestBundle\"\n >;\n updatedAt: string;\n}\n\nexport interface SharedRemoteContainerState {\n accessEpoch: number;\n accessManifestHash: string;\n createdAt: string;\n metadataDocumentId: string;\n referencedPrincipalHeads: ReferencedPrincipalStateResponse[];\n updatedAt: string;\n writerProjection: ContainerWriterProjectionResponse | null;\n}\n\nexport interface SharedContainerState {\n container: ContainerState[\"container\"];\n record: ContainerState[\"record\"];\n}\n\nexport type SharedContainerStateResult =\n | { status: \"missing\" }\n | { status: \"confirmed\" }\n | (SharedContainerState & {\n status: \"identity-superseded\" | \"persisted\";\n });\n\nexport type RemoteContainer = HydratedRemoteContainer;\n\nexport type ContainerMetadataDocumentState = Awaited<\n ReturnType<typeof createInitializedContainerMetadataDocument>\n>[\"doc\"];\n\nexport interface CreatedChildContainerState {\n containerState: ContainerState;\n initialUpdate: Uint8Array;\n shouldRequestSync: boolean;\n}\n\nexport interface ContainerIntentSyncState {\n containersById: ReadonlyMap<string, ContainerState>;\n /**\n * Metadata update ids this device sent, so their own update echoes are\n * classified as self-authored (shared with the metadata sync).\n */\n locallyAcceptedMetadataUpdateIds?: Set<string> | undefined;\n /** Metadata document ids of folder creates in flight (shared with hints). */\n plannedMetadataDocumentIds?: Set<string> | undefined;\n persistence: ContainerContentsPersistence;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n /**\n * Verifies that a listed container is this device's pending create before it\n * is adopted and returns where it sits; `assertContainerCreateAdoptable`\n * unless a test replaces it.\n */\n verifyCreateAdoption?: (input: {\n readonly containerId: string;\n readonly expectedOrganizationId: string;\n }) => Promise<AdoptedContainerPlacement>;\n}\n\n/** Where a verified listed create committed, and where its container is now. */\nexport interface AdoptedContainerPlacement {\n /** The parent its signed epoch-1 `container.create` named. */\n readonly createdParentId: string;\n /** The verified head's parent; another writer may have moved it since. */\n readonly currentParentId: string;\n}\n\nexport type ContainerCreateIntentSyncState = ContainerIntentSyncState;\n\nexport type ContainerCreateIntentSyncHost = Pick<\n RemoteContainerHydrationHost,\n \"persistContainerState\"\n>;\n\nexport interface ContainerCreateIntentSyncInput {\n host: ContainerCreateIntentSyncHost;\n /** A parent whose own create has not settled takes no child create. */\n isCreatePending: (containerId: string) => boolean;\n isCurrent: () => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n intent: ContainerCreateIntentRecord;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerCreateIntentSyncState;\n}\n\nexport type ContainerMoveIntentSyncState = ContainerIntentSyncState;\n\nexport type ContainerMoveIntentSyncHost = Pick<\n RemoteContainerHydrationHost,\n \"persistContainerState\" | \"updateSnapshot\"\n>;\n\n/** Later passes for moves the server accepted but no pass settled. */\nexport interface AcceptedMoveRetry {\n /** The server holds the move: schedule a replay to settle it. */\n arm: (intentId: string) => void;\n /** A replay failed; continue an accepted move's retries, if it has some. */\n resume: (intentId: string) => void;\n}\n\nexport interface ContainerMoveIntentSyncInput {\n host: ContainerMoveIntentSyncHost;\n isCurrent: () => boolean;\n /** A folder whose create has not settled takes no structural writes. */\n isCreatePending: (containerId: string) => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n intent: ContainerMoveIntentRecord;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n acceptedMoveRetry: AcceptedMoveRetry;\n state: ContainerMoveIntentSyncState;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ContainerSystemSlot } from \"@tearleads/validators/containerSystemSlot\";\nimport type {\n ContainerDeleteResponse,\n ContainerWriterProjectionResponse,\n ReferencedPrincipalStateResponse,\n} from \"@tearleads/validators/response\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport type { DocumentRecord } from \"../../../data/persistence/documents/records\";\nimport type {\n createRemoteContainer,\n moveRemoteContainer,\n shareRemoteContainer,\n} from \"../../containers\";\nimport type { createRemoteDocument } from \"../../documents\";\nimport type { shareRemoteContainerWithGroup } from \"../../organizations/groupShare\";\nimport type { CurrentPrincipalPolicyReader } from \"../../principals/currentPrincipalPolicyReader\";\nimport type { PrincipalPolicyHistoryReader } from \"../../principals/principalPolicyHistoryReader\";\nimport type {\n ContainerContentsPersistence,\n ContainerCreateIntentRecord,\n ContainerMoveIntentRecord,\n} from \"../containerPersistence\";\nimport type { createInitializedContainerMetadataDocument } from \"../metadata/containerMetadataDocument\";\nimport type {\n ContainerState,\n RemoteContainer as HydratedRemoteContainer,\n RemoteContainerHydrationHost,\n} from \"../remoteHydration/types\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\n\nexport type ContainerWorkflowApi = Parameters<\n typeof createRemoteContainer\n>[0][\"apiClient\"] &\n Parameters<typeof shareRemoteContainer>[0][\"apiClient\"] &\n Parameters<typeof shareRemoteContainerWithGroup>[0][\"apiClient\"] &\n Parameters<typeof moveRemoteContainer>[0][\"apiClient\"] &\n Parameters<typeof createRemoteDocument>[0][\"apiClient\"] &\n Partial<PrincipalPolicyHistoryReader> &\n CurrentPrincipalPolicyReader & {\n deleteContainerResult(\n containerId: string,\n options?: {\n expectedPaymentRequiredOrganizationId?: string;\n reportErrors?: boolean;\n requiredAncestorId?: string | undefined;\n },\n ): Promise<\n | { data: ContainerDeleteResponse; ok: true }\n | {\n ok: false;\n report: () => void;\n status: number | null;\n }\n >;\n primeContainerWriterProjection(\n containerId: string,\n projection: ContainerWriterProjectionResponse,\n ): void;\n };\n\nexport interface ContainerWorkflowRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n readonly apiClient: ContainerWorkflowApi;\n}\n\nexport interface CreatedRemoteContainerState {\n accessManifestHash: string;\n /**\n * The metadata seed the create committed, settled by the caller instead of\n * a first metadata sync. Absent when the create carried no seed or fell back\n * to a plain create.\n */\n metadataSeed?:\n | { readonly commitLsn: string | null; readonly updateId: string }\n | undefined;\n /** The slot this client signed into the create; never the server's echo. */\n systemSlot: ContainerSystemSlot | null;\n containerId: string;\n createdAt: string;\n metadataDocumentId: string;\n organizationId: string;\n /** A created container is a child; the acknowledgement echoed this parent. */\n parentId: string;\n persistedMetadataState: Pick<\n DocumentRecord,\n | \"documentId\"\n | \"contentKeyBundle\"\n | \"documentKekTargets\"\n | \"documentManifestBundle\"\n >;\n updatedAt: string;\n}\n\nexport interface SharedRemoteContainerState {\n accessEpoch: number;\n accessManifestHash: string;\n createdAt: string;\n metadataDocumentId: string;\n referencedPrincipalHeads: ReferencedPrincipalStateResponse[];\n updatedAt: string;\n writerProjection: ContainerWriterProjectionResponse | null;\n}\n\nexport interface SharedContainerState {\n container: ContainerState[\"container\"];\n record: ContainerState[\"record\"];\n}\n\nexport type SharedContainerStateResult =\n | { status: \"missing\" }\n | { status: \"confirmed\" }\n | (SharedContainerState & {\n status: \"identity-superseded\" | \"persisted\";\n });\n\nexport type RemoteContainer = HydratedRemoteContainer;\n\nexport type ContainerMetadataDocumentState = Awaited<\n ReturnType<typeof createInitializedContainerMetadataDocument>\n>[\"doc\"];\n\nexport interface CreatedChildContainerState {\n containerState: ContainerState;\n initialUpdate: Uint8Array;\n shouldRequestSync: boolean;\n}\n\nexport interface ContainerIntentSyncState {\n containersById: ReadonlyMap<string, ContainerState>;\n /**\n * Metadata update ids this device sent, so their own update echoes are\n * classified as self-authored (shared with the metadata sync).\n */\n locallyAcceptedMetadataUpdateIds?: Set<string> | undefined;\n /** Metadata document ids of folder creates in flight (shared with hints). */\n plannedMetadataDocumentIds?: Set<string> | undefined;\n persistence: ContainerContentsPersistence;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n /**\n * Verifies that a listed container is this device's pending create before it\n * is adopted and returns where it sits; `assertContainerCreateAdoptable`\n * unless a test replaces it.\n */\n verifyCreateAdoption?: (input: {\n readonly containerId: string;\n readonly expectedOrganizationId: string;\n }) => Promise<AdoptedContainerPlacement>;\n}\n\n/** Where a verified listed create committed, and where its container is now. */\nexport interface AdoptedContainerPlacement {\n /** The parent its signed epoch-1 `container.create` named. */\n readonly createdParentId: string;\n /** The verified head's parent; another writer may have moved it since. */\n readonly currentParentId: string;\n}\n\nexport type ContainerCreateIntentSyncState = ContainerIntentSyncState;\n\nexport type ContainerCreateIntentSyncHost = Pick<\n RemoteContainerHydrationHost,\n \"persistContainerState\"\n>;\n\nexport interface ContainerCreateIntentSyncInput {\n host: ContainerCreateIntentSyncHost;\n /** A parent whose own create has not settled takes no child create. */\n isCreatePending: (containerId: string) => boolean;\n isCurrent: () => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n intent: ContainerCreateIntentRecord;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerCreateIntentSyncState;\n}\n\nexport type ContainerMoveIntentSyncState = ContainerIntentSyncState;\n\nexport type ContainerMoveIntentSyncHost = Pick<\n RemoteContainerHydrationHost,\n \"persistContainerState\" | \"updateSnapshot\"\n>;\n\n/** Later passes for moves the server accepted but no pass settled. */\nexport interface AcceptedMoveRetry {\n /** The server holds the move: schedule a replay to settle it. */\n arm: (intentId: string) => void;\n /** A replay failed; continue an accepted move's retries, if it has some. */\n resume: (intentId: string) => void;\n}\n\nexport interface ContainerMoveIntentSyncInput {\n host: ContainerMoveIntentSyncHost;\n isCurrent: () => boolean;\n /** A folder whose create has not settled takes no structural writes. */\n isCreatePending: (containerId: string) => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n intent: ContainerMoveIntentRecord;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n acceptedMoveRetry: AcceptedMoveRetry;\n state: ContainerMoveIntentSyncState;\n}\n"]}
@@ -7,7 +7,7 @@ export async function saveAppliedContainerDocumentWatermark(input) {
7
7
  if (!listedDocuments.nextWatermark) {
8
8
  return;
9
9
  }
10
- await saveContainerDocumentWatermark(containerId, listedDocuments.nextWatermark);
10
+ await saveContainerDocumentWatermark(containerId, listedDocuments.nextWatermark, input.stillCurrent);
11
11
  }
12
12
  /**
13
13
  * Verifies and applies one container's completed listing under a discovery
@@ -31,14 +31,14 @@ export async function applyListedContainerDocuments(options, generation, listedD
31
31
  return null;
32
32
  const verifiedInputs = verification.inputs;
33
33
  const discoveredDocuments = verifiedInputs.length
34
- ? await upsertDiscoveredDocuments(verifiedInputs)
34
+ ? await upsertDiscoveredDocuments(verifiedInputs, verification.isCurrent)
35
35
  : [];
36
36
  if (verifiedInputs.length)
37
37
  await options.replaceDocumentLinksBatch(await withoutDeferredDocumentLinks(verifiedInputs.map((document) => ({
38
38
  documentId: document.documentId,
39
39
  accessEpoch: document.accessEpoch,
40
40
  containerIds: document.linkedContainerIds,
41
- })), discoveredDocuments));
41
+ })), discoveredDocuments), verification.isCurrent);
42
42
  const tombstoneDocumentSummaries = await settleContainerDocumentTombstones({
43
43
  containerIds: [containerId],
44
44
  store: options,
@@ -51,7 +51,10 @@ export async function applyListedContainerDocuments(options, generation, listedD
51
51
  containerId,
52
52
  listedDocuments,
53
53
  saveContainerDocumentWatermark: options.saveContainerDocumentWatermark,
54
+ stillCurrent: verification.isCurrent,
54
55
  });
56
+ if (!(await verification.isCurrent()))
57
+ return null;
55
58
  if (listedDocuments.isFullListing) {
56
59
  // Initial probes consume ids as discovery hints and verify their own heads.
57
60
  options.onFullListing?.(listedDocuments.items.map((document) => document.id));
@@ -1 +1 @@
1
- {"version":3,"file":"documentDiscoveryApply.js","sourceRoot":"","sources":["documentDiscoveryApply.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport { dedupeReferencedPrincipalStates } from \"../../../data/keyingProjectionVerification/principalPolicy/principalPolicyCache\";\nimport { withoutDeferredDocumentLinks } from \"../documentLinks/deferredDocumentLinks\";\nimport { settleContainerDocumentTombstones } from \"../tombstones/documentTombstoneGate\";\nimport { getApplicableDocumentTombstones } from \"./documentDiscoveryInputs\";\nimport type {\n DiscoverContainerDocumentsOptions,\n ListedContainerDocuments,\n} from \"./documentDiscoveryTypes\";\n\n/** A discovery lane's local stores; the listing itself is supplied by the caller. */\nexport type ContainerDocumentDiscoveryLane = Omit<\n DiscoverContainerDocumentsOptions,\n | \"beginDocumentDiscovery\"\n | \"listContainerDocumentLanes\"\n | \"loadContainerDocumentWatermark\"\n>;\n\nexport async function saveAppliedContainerDocumentWatermark(input: {\n containerId: string;\n listedDocuments: ListedContainerDocuments;\n saveContainerDocumentWatermark: DiscoverContainerDocumentsOptions[\"saveContainerDocumentWatermark\"];\n}) {\n const { containerId, listedDocuments, saveContainerDocumentWatermark } =\n input;\n if (!listedDocuments.nextWatermark) {\n return;\n }\n\n await saveContainerDocumentWatermark(\n containerId,\n listedDocuments.nextWatermark,\n );\n}\n\n/**\n * Verifies and applies one container's completed listing under a discovery\n * generation captured before the listing request, then advances its\n * watermark. Returns `null` when nothing was applied.\n */\nexport async function applyListedContainerDocuments(\n options: ContainerDocumentDiscoveryLane,\n generation: number,\n listedDocuments: ListedContainerDocuments,\n): Promise<ReadonlyArray<DocumentSummary> | null> {\n const { containerId, upsertDiscoveredDocuments } = options;\n await options.cacheReferencedPrincipalPolicies?.(\n dedupeReferencedPrincipalStates(\n listedDocuments.items.flatMap(\n (document) => document.referencedPrincipals,\n ),\n ),\n );\n\n const verification = await options.verifyDiscoveredDocuments(\n listedDocuments.items.map((document) => ({\n accessEpoch: document.currentAccessEpoch,\n accessStateHash: document.currentAccessStateHash,\n containerId,\n listedContainerIds: [containerId],\n createdAt: document.createdAt,\n documentId: document.id,\n effectiveAccessLevel: document.effectiveAccessLevel,\n linkedContainerIds: document.linkedContainerIds,\n })),\n [containerId],\n generation,\n getApplicableDocumentTombstones(listedDocuments),\n );\n if (!(await verification.isCurrent())) return null;\n const verifiedInputs = verification.inputs;\n const discoveredDocuments = verifiedInputs.length\n ? await upsertDiscoveredDocuments(verifiedInputs)\n : [];\n\n if (verifiedInputs.length)\n await options.replaceDocumentLinksBatch(\n await withoutDeferredDocumentLinks(\n verifiedInputs.map((document) => ({\n documentId: document.documentId,\n accessEpoch: document.accessEpoch,\n containerIds: document.linkedContainerIds,\n })),\n discoveredDocuments,\n ),\n );\n\n const tombstoneDocumentSummaries = await settleContainerDocumentTombstones({\n containerIds: [containerId],\n store: options,\n tombstones: getApplicableDocumentTombstones(listedDocuments),\n });\n\n const complete = await verification.commit();\n if (!(await verification.isCurrent())) return null;\n await saveAppliedContainerDocumentWatermark({\n containerId,\n listedDocuments,\n saveContainerDocumentWatermark: options.saveContainerDocumentWatermark,\n });\n\n if (listedDocuments.isFullListing) {\n // Initial probes consume ids as discovery hints and verify their own heads.\n options.onFullListing?.(\n listedDocuments.items.map((document) => document.id),\n );\n }\n\n return !complete &&\n discoveredDocuments.length === 0 &&\n tombstoneDocumentSummaries.length === 0\n ? null\n : [...discoveredDocuments, ...tombstoneDocumentSummaries];\n}\n"]}
1
+ {"version":3,"file":"documentDiscoveryApply.js","sourceRoot":"","sources":["documentDiscoveryApply.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport { dedupeReferencedPrincipalStates } from \"../../../data/keyingProjectionVerification/principalPolicy/principalPolicyCache\";\nimport { withoutDeferredDocumentLinks } from \"../documentLinks/deferredDocumentLinks\";\nimport { settleContainerDocumentTombstones } from \"../tombstones/documentTombstoneGate\";\nimport { getApplicableDocumentTombstones } from \"./documentDiscoveryInputs\";\nimport type {\n DiscoverContainerDocumentsOptions,\n ListedContainerDocuments,\n} from \"./documentDiscoveryTypes\";\n\n/** A discovery lane's local stores; the listing itself is supplied by the caller. */\nexport type ContainerDocumentDiscoveryLane = Omit<\n DiscoverContainerDocumentsOptions,\n | \"beginDocumentDiscovery\"\n | \"listContainerDocumentLanes\"\n | \"loadContainerDocumentWatermark\"\n>;\n\nexport async function saveAppliedContainerDocumentWatermark(input: {\n containerId: string;\n listedDocuments: ListedContainerDocuments;\n saveContainerDocumentWatermark: DiscoverContainerDocumentsOptions[\"saveContainerDocumentWatermark\"];\n stillCurrent: () => Promise<boolean>;\n}) {\n const { containerId, listedDocuments, saveContainerDocumentWatermark } =\n input;\n if (!listedDocuments.nextWatermark) {\n return;\n }\n\n await saveContainerDocumentWatermark(\n containerId,\n listedDocuments.nextWatermark,\n input.stillCurrent,\n );\n}\n\n/**\n * Verifies and applies one container's completed listing under a discovery\n * generation captured before the listing request, then advances its\n * watermark. Returns `null` when nothing was applied.\n */\nexport async function applyListedContainerDocuments(\n options: ContainerDocumentDiscoveryLane,\n generation: number,\n listedDocuments: ListedContainerDocuments,\n): Promise<ReadonlyArray<DocumentSummary> | null> {\n const { containerId, upsertDiscoveredDocuments } = options;\n await options.cacheReferencedPrincipalPolicies?.(\n dedupeReferencedPrincipalStates(\n listedDocuments.items.flatMap(\n (document) => document.referencedPrincipals,\n ),\n ),\n );\n\n const verification = await options.verifyDiscoveredDocuments(\n listedDocuments.items.map((document) => ({\n accessEpoch: document.currentAccessEpoch,\n accessStateHash: document.currentAccessStateHash,\n containerId,\n listedContainerIds: [containerId],\n createdAt: document.createdAt,\n documentId: document.id,\n effectiveAccessLevel: document.effectiveAccessLevel,\n linkedContainerIds: document.linkedContainerIds,\n })),\n [containerId],\n generation,\n getApplicableDocumentTombstones(listedDocuments),\n );\n if (!(await verification.isCurrent())) return null;\n const verifiedInputs = verification.inputs;\n const discoveredDocuments = verifiedInputs.length\n ? await upsertDiscoveredDocuments(verifiedInputs, verification.isCurrent)\n : [];\n\n if (verifiedInputs.length)\n await options.replaceDocumentLinksBatch(\n await withoutDeferredDocumentLinks(\n verifiedInputs.map((document) => ({\n documentId: document.documentId,\n accessEpoch: document.accessEpoch,\n containerIds: document.linkedContainerIds,\n })),\n discoveredDocuments,\n ),\n verification.isCurrent,\n );\n\n const tombstoneDocumentSummaries = await settleContainerDocumentTombstones({\n containerIds: [containerId],\n store: options,\n tombstones: getApplicableDocumentTombstones(listedDocuments),\n });\n\n const complete = await verification.commit();\n if (!(await verification.isCurrent())) return null;\n await saveAppliedContainerDocumentWatermark({\n containerId,\n listedDocuments,\n saveContainerDocumentWatermark: options.saveContainerDocumentWatermark,\n stillCurrent: verification.isCurrent,\n });\n if (!(await verification.isCurrent())) return null;\n\n if (listedDocuments.isFullListing) {\n // Initial probes consume ids as discovery hints and verify their own heads.\n options.onFullListing?.(\n listedDocuments.items.map((document) => document.id),\n );\n }\n\n return !complete &&\n discoveredDocuments.length === 0 &&\n tombstoneDocumentSummaries.length === 0\n ? null\n : [...discoveredDocuments, ...tombstoneDocumentSummaries];\n}\n"]}
@@ -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"]}
@@ -1,6 +1,7 @@
1
1
  import { isKeyingVerificationError, runWithSecurityIncidentReporting, } from "../../../../data/keyingProjectionVerification/error.js";
2
2
  import { isProjectionVerificationCancelledError } from "../../../../data/keyingProjectionVerification/types.js";
3
3
  import { heldContainerBinding, listingRepeatsHeldOrdinaryBinding, } from "../../../../data/persistence/container-contents/containerBinding.js";
4
+ import { invalidateContainerProjectionCaches } from "../../container-state/projectionCache.js";
4
5
  import { installContainerMetadataRecord } from "../../metadata/metadataPersistence.js";
5
6
  import { projectionGeneration } from "../../projectionGeneration.js";
6
7
  import { verifyRemoteContainerDestination } from "../destination/verifiedDestination.js";
@@ -165,6 +166,21 @@ async function updateExistingRemoteContainerState(input) {
165
166
  });
166
167
  return existingState;
167
168
  }
169
+ /** A pull can observe the same manifest change as a missed realtime hint. */
170
+ function invalidateListedProjectionCaches(input) {
171
+ const held = input.state.containersById.get(input.remoteContainer.id);
172
+ if (held &&
173
+ (held.record.accessStateHash !==
174
+ input.remoteContainer.metadataAccessStateHash ||
175
+ (held.container.effectiveAccessLevel ?? "read") !==
176
+ input.remoteContainer.effectiveAccessLevel)) {
177
+ // Eviction admits no remote state. Drop citations before verification can
178
+ // advance local checkpoints past those cached container/document paths.
179
+ invalidateContainerProjectionCaches(input.state, [
180
+ input.remoteContainer.id,
181
+ ]);
182
+ }
183
+ }
168
184
  export async function upsertRemoteContainerState(input) {
169
185
  let expectedPlacementCheckpoint;
170
186
  const heldState = input.state.containersById.get(input.remoteContainer.id);
@@ -178,6 +194,7 @@ export async function upsertRemoteContainerState(input) {
178
194
  (await input.state.persistence.isSupersededContainerBinding(input.state.runtime.infra.execSql, { containerId: listed.id, organizationId: listed.organizationId }));
179
195
  if (input.isCurrent?.() === false)
180
196
  return null;
197
+ invalidateListedProjectionCaches(input);
181
198
  const verified = await verifyRemoteContainerDestination({
182
199
  ...input,
183
200
  heldBinding,
@@ -1 +1 @@
1
- {"version":3,"file":"remoteContainerState.js","sourceRoot":"","sources":["remoteContainerState.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccessManifestCheckpoint } from \"@tearleads/crypto\";\nimport {\n isKeyingVerificationError,\n runWithSecurityIncidentReporting,\n} from \"../../../../data/keyingProjectionVerification/error\";\nimport { isProjectionVerificationCancelledError } from \"../../../../data/keyingProjectionVerification/types\";\nimport {\n heldContainerBinding,\n listingRepeatsHeldOrdinaryBinding,\n} from \"../../../../data/persistence/container-contents/containerBinding\";\nimport type { ContainerHydrationTombstone } from \"../../containerPersistence\";\nimport { installContainerMetadataRecord } from \"../../metadata/metadataPersistence\";\nimport { projectionGeneration } from \"../../projectionGeneration\";\nimport type { PrefetchedDestinationProjection } from \"../destination/destinationPrefetch\";\nimport { verifyRemoteContainerDestination } from \"../destination/verifiedDestination\";\nimport { rebindReHomedContainer } from \"../heldContainerRebind\";\nimport {\n moveIndexedContainerChild,\n removeIndexedContainerChild,\n} from \"../lanes/childIndex\";\nimport {\n reconcileLocalOnlyRootContainers,\n reconcileLocalOnlySystemContainers,\n} from \"../reconciliation\";\nimport type {\n ContainerChildIndex,\n ContainerState,\n RemoteContainer,\n RemoteContainerHydrationHost,\n RemoteContainerHydrationState,\n} from \"../types\";\nimport { insertRemoteContainerState } from \"./insertRemoteContainer\";\nimport {\n applyRemoteContainerTimestamps,\n remoteContainerHydrationSaveOptions,\n resolveRemoteContainerHydrationLocalUpdatedAt,\n} from \"./remoteContainerTimestamps\";\n\n// Container ids (restricted to the inbound page) that carry an unsynced local\n// create or move intent. Such a container's parent and local-edit timestamp are\n// owned by its structural-intent lane until that lane reconciles, so inbound\n// hydration must not revert parentId to the server value nor collapse\n// localUpdatedAt — doing so silently undoes a queued move and falsely reads\n// \"synced\". Move intents are read via listUnsyncedMoveIntents so a blocked\n// move — one whose destination parent has not synced yet, the common boot-time\n// case — is protected too. Pending *metadata*\n// updates are handled separately (listRemoteContainerIdsWithPendingMetadataUpdates);\n// structural intents live in the local outbox's container lanes, which that\n// query does not cover.\nexport async function listRemoteContainerIdsWithPendingStructuralIntents(input: {\n remoteContainers: ReadonlyArray<RemoteContainer>;\n state: RemoteContainerHydrationState;\n}): Promise<Set<string>> {\n if (input.remoteContainers.length === 0) {\n return new Set();\n }\n const remoteContainerIds = new Set(\n input.remoteContainers.map((remoteContainer) => remoteContainer.id),\n );\n\n const execSql = input.state.runtime.infra.execSql;\n const [pendingCreateIntents, unsyncedMoveIntents] = await Promise.all([\n input.state.persistence.listPendingCreateIntents(execSql),\n input.state.persistence.listUnsyncedMoveIntents(execSql),\n ]);\n const containerIdsWithPendingStructuralIntents = new Set<string>();\n for (const intent of pendingCreateIntents) {\n if (remoteContainerIds.has(intent.containerId)) {\n containerIdsWithPendingStructuralIntents.add(intent.containerId);\n }\n }\n for (const intent of unsyncedMoveIntents) {\n if (remoteContainerIds.has(intent.containerId)) {\n containerIdsWithPendingStructuralIntents.add(intent.containerId);\n }\n }\n return containerIdsWithPendingStructuralIntents;\n}\n\nexport async function listRemoteContainerIdsWithPendingMetadataUpdates(input: {\n remoteContainers: ReadonlyArray<RemoteContainer>;\n state: RemoteContainerHydrationState;\n}): Promise<Set<string>> {\n const containerIds = input.remoteContainers.flatMap((remoteContainer) => {\n const previousLocalUpdatedAt = input.state.containersById.get(\n remoteContainer.id,\n )?.container.localUpdatedAt;\n\n return previousLocalUpdatedAt &&\n previousLocalUpdatedAt.localeCompare(remoteContainer.updatedAt) > 0\n ? [remoteContainer.id]\n : [];\n });\n if (containerIds.length === 0) {\n return new Set();\n }\n\n const execSql = input.state.runtime.infra.execSql;\n return new Set(\n await input.state.persistence.listContainerIdsWithPendingUpdates(\n execSql,\n containerIds,\n ),\n );\n}\n\nfunction removeMissingHydratedContainer(input: {\n childIdsByParentId?: ContainerChildIndex | undefined;\n existingState: ContainerState;\n host: RemoteContainerHydrationHost;\n previousParentId: string | null;\n remoteContainer: RemoteContainer;\n state: RemoteContainerHydrationState;\n}): void {\n const { existingState, remoteContainer, state } = input;\n if (state.containersById.get(remoteContainer.id) !== existingState) {\n return;\n }\n\n state.containersById.delete(remoteContainer.id);\n if (input.childIdsByParentId) {\n removeIndexedContainerChild(\n input.childIdsByParentId,\n remoteContainer.id,\n input.previousParentId,\n );\n input.childIdsByParentId.delete(remoteContainer.id);\n }\n input.host.updateSnapshot();\n}\n\nfunction createUpdatedRemoteContainerState(\n existingState: ContainerState,\n remoteContainer: RemoteContainer,\n): ContainerState {\n return {\n ...existingState,\n container: applyRemoteContainerTimestamps(\n existingState.container,\n remoteContainer,\n ),\n containerWriterProjection: null,\n metadataReferencedPrincipals: remoteContainer.metadataReferencedPrincipals,\n };\n}\n\nfunction installUpdatedRemoteContainerState(\n existingState: ContainerState,\n nextState: ContainerState,\n host: RemoteContainerHydrationHost,\n): void {\n const wasLocalOnly = existingState.container.serverCreatedAt == null;\n existingState.container = nextState.container;\n existingState.containerWriterProjection = nextState.containerWriterProjection;\n existingState.metadataReferencedPrincipals =\n nextState.metadataReferencedPrincipals;\n // The snapshot copied the live generation when it was taken; a hint that\n // landed during the persist leaves the live metadata projection dropped.\n if (projectionGeneration(existingState) === projectionGeneration(nextState))\n existingState.metadataWriterProjection = nextState.metadataWriterProjection;\n installContainerMetadataRecord(existingState, nextState.record);\n // A document pass may have deferred while this root was awaiting proof.\n // The first durable remote acknowledgement makes its pending creates runnable.\n if (wasLocalOnly && existingState.container.serverCreatedAt != null)\n host.requestDocumentPriming?.();\n}\n\nasync function updateExistingRemoteContainerState(input: {\n childIdsByParentId?: ContainerChildIndex | undefined;\n containerIdsWithPendingMetadataUpdates: ReadonlySet<string>;\n containerIdsWithPendingStructuralIntents: ReadonlySet<string>;\n host: RemoteContainerHydrationHost;\n isCurrent?: (() => boolean) | undefined;\n existingState: ContainerState;\n remoteContainer: RemoteContainer;\n state: RemoteContainerHydrationState;\n}): Promise<ContainerState | null> {\n const { childIdsByParentId, existingState, host, remoteContainer, state } =\n input;\n const previousParentId = existingState.container.parentId;\n const previousLocalUpdatedAt = existingState.container.localUpdatedAt;\n // A queued, not-yet-synced local move/create owns this container's parent\n // until its intent lane reconciles. Omit the remote parent when the page scan\n // saw one; persistence also rechecks the intent transactionally so a move\n // created after that scan wins. Both paths preserve the durable local clock.\n const hasPendingStructuralIntent =\n input.containerIdsWithPendingStructuralIntents.has(remoteContainer.id);\n const nextParentId = hasPendingStructuralIntent\n ? undefined\n : remoteContainer.parentId;\n const localUpdatedAt = resolveRemoteContainerHydrationLocalUpdatedAt({\n containerIdsWithPendingMetadataUpdates:\n input.containerIdsWithPendingMetadataUpdates,\n hasPendingStructuralIntent,\n previousLocalUpdatedAt,\n remoteContainer,\n });\n const nextState = createUpdatedRemoteContainerState(\n existingState,\n remoteContainer,\n );\n const persistenceResult = await host.persistContainerState(\n nextState,\n {\n accessEpoch: remoteContainer.metadataAccessEpoch,\n accessStateHash: remoteContainer.metadataAccessStateHash,\n documentId: remoteContainer.metadataDocumentId,\n effectiveAccessLevel: remoteContainer.effectiveAccessLevel,\n metadataDocumentId: remoteContainer.metadataDocumentId,\n systemSlot: remoteContainer.systemSlot ?? null,\n organizationId: remoteContainer.organizationId,\n ...(nextParentId !== undefined ? { parentId: nextParentId } : {}),\n },\n false,\n remoteContainerHydrationSaveOptions({\n localUpdatedAt,\n remoteContainer,\n }),\n {\n preserveDurableStructureWhenPending: true,\n },\n );\n if (persistenceResult.status === \"missing\") {\n // Hydration persists a detached candidate so an in-flight response cannot\n // mutate mapped state before its guards settle. Retire the exact state this\n // hydration read because store-level reference cleanup cannot see the clone.\n removeMissingHydratedContainer({\n childIdsByParentId,\n existingState,\n host,\n previousParentId,\n remoteContainer,\n state,\n });\n return null;\n }\n if (persistenceResult.status !== \"persisted\") return null;\n const { record: nextRecord } = persistenceResult;\n installContainerMetadataRecord(nextState, nextRecord);\n if (input.isCurrent?.() === false) {\n return existingState;\n }\n nextState.container = {\n ...nextState.container,\n metadataDocumentId: remoteContainer.metadataDocumentId,\n organizationId: remoteContainer.organizationId,\n parentId: nextState.container.parentId,\n };\n\n installUpdatedRemoteContainerState(existingState, nextState, host);\n moveIndexedContainerChild(\n childIdsByParentId,\n remoteContainer.id,\n previousParentId,\n existingState.container.parentId,\n );\n await reconcileLocalOnlyRootContainers({\n childIdsByParentId,\n isCurrent: input.isCurrent,\n remoteRootState: existingState,\n requestDocumentPriming: host.requestDocumentPriming,\n state,\n });\n return existingState;\n}\n\ninterface UpsertRemoteContainerStateInput {\n childIdsByParentId?: ContainerChildIndex | undefined;\n containerIdsWithPendingMetadataUpdates: ReadonlySet<string>;\n containerIdsWithPendingStructuralIntents: ReadonlySet<string>;\n host: RemoteContainerHydrationHost;\n expectedHydrationTombstone?: ContainerHydrationTombstone | null | undefined;\n isCurrent?: (() => boolean) | undefined;\n prefetchedProjection?: PrefetchedDestinationProjection | undefined;\n remoteContainer: RemoteContainer;\n state: RemoteContainerHydrationState;\n}\n\nexport async function upsertRemoteContainerState(\n input: UpsertRemoteContainerStateInput,\n): Promise<ContainerState | null> {\n let expectedPlacementCheckpoint: AccessManifestCheckpoint | undefined;\n const heldState = input.state.containersById.get(input.remoteContainer.id);\n const heldBinding = heldState\n ? heldContainerBinding(heldState)\n : await input.state.persistence.loadHeldContainerBinding(\n input.state.runtime.infra.execSql,\n input.remoteContainer.id,\n );\n const listed = input.remoteContainer;\n // A folder re-homed away from the listed organization never returns there,\n // whatever binding (possibly none, after a reset) the device holds now.\n const supersededListing =\n !listingRepeatsHeldOrdinaryBinding(heldBinding, listed) &&\n (await input.state.persistence.isSupersededContainerBinding(\n input.state.runtime.infra.execSql,\n { containerId: listed.id, organizationId: listed.organizationId },\n ));\n if (input.isCurrent?.() === false) return null;\n const verified = await verifyRemoteContainerDestination({\n ...input,\n heldBinding,\n supersededListing,\n refresh: !!input.expectedHydrationTombstone,\n onVerifiedCheckpoint: (checkpoint) => {\n expectedPlacementCheckpoint = checkpoint;\n },\n });\n if (!verified || input.isCurrent?.() === false) return null;\n input = { ...input, remoteContainer: verified };\n const existingState = input.state.containersById.get(\n input.remoteContainer.id,\n );\n const remoteState = await runWithSecurityIncidentReporting(\n input.state.runtime.util.reportSecurityIncident,\n {\n objectId: verified.id,\n objectKind: \"container\",\n operation: \"container.binding.persist\",\n organizationId: verified.organizationId,\n },\n async () =>\n (await rebindReHomedContainer({\n existingState,\n heldBinding,\n isCurrent: input.isCurrent,\n state: input.state,\n verified,\n }))\n ? persistVerifiedRemoteContainer({\n ...input,\n existingState,\n expectedPlacementCheckpoint,\n })\n : null,\n );\n if (!remoteState) {\n return null;\n }\n await reconcileLocalOnlySystemContainers({\n childIdsByParentId: input.childIdsByParentId,\n isCurrent: input.isCurrent,\n requestDocumentPriming: input.host.requestDocumentPriming,\n remoteSystemState: remoteState,\n state: input.state,\n });\n return remoteState;\n}\n\n/**\n * Upsert one listed folder, isolating a rejected proof or binding. The refusal\n * was already reported; returning no state leaves the item unapplied without\n * blocking independent folders.\n */\nexport async function upsertIsolatedRemoteContainerState(\n input: UpsertRemoteContainerStateInput,\n): Promise<ContainerState | null> {\n try {\n return await upsertRemoteContainerState(input);\n } catch (error) {\n if (\n !isKeyingVerificationError(error) &&\n !isProjectionVerificationCancelledError(error)\n )\n throw error;\n return null;\n }\n}\n\nfunction persistVerifiedRemoteContainer(\n input: UpsertRemoteContainerStateInput & {\n existingState: ContainerState | undefined;\n expectedPlacementCheckpoint: AccessManifestCheckpoint | undefined;\n },\n): Promise<ContainerState | null> {\n const { existingState, expectedPlacementCheckpoint } = input;\n return existingState\n ? updateExistingRemoteContainerState({\n childIdsByParentId: input.childIdsByParentId,\n containerIdsWithPendingMetadataUpdates:\n input.containerIdsWithPendingMetadataUpdates,\n containerIdsWithPendingStructuralIntents:\n input.containerIdsWithPendingStructuralIntents,\n existingState,\n host: input.host,\n isCurrent: input.isCurrent,\n remoteContainer: input.remoteContainer,\n state: input.state,\n })\n : insertRemoteContainerState({\n childIdsByParentId: input.childIdsByParentId,\n host: input.host,\n expectedHydrationTombstone: input.expectedHydrationTombstone ?? null,\n expectedPlacementCheckpoint,\n isCurrent: input.isCurrent,\n remoteContainer: input.remoteContainer,\n state: input.state,\n });\n}\n"]}
1
+ {"version":3,"file":"remoteContainerState.js","sourceRoot":"","sources":["remoteContainerState.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccessManifestCheckpoint } from \"@tearleads/crypto\";\nimport {\n isKeyingVerificationError,\n runWithSecurityIncidentReporting,\n} from \"../../../../data/keyingProjectionVerification/error\";\nimport { isProjectionVerificationCancelledError } from \"../../../../data/keyingProjectionVerification/types\";\nimport {\n heldContainerBinding,\n listingRepeatsHeldOrdinaryBinding,\n} from \"../../../../data/persistence/container-contents/containerBinding\";\nimport { invalidateContainerProjectionCaches } from \"../../container-state/projectionCache\";\nimport type { ContainerHydrationTombstone } from \"../../containerPersistence\";\nimport { installContainerMetadataRecord } from \"../../metadata/metadataPersistence\";\nimport { projectionGeneration } from \"../../projectionGeneration\";\nimport type { PrefetchedDestinationProjection } from \"../destination/destinationPrefetch\";\nimport { verifyRemoteContainerDestination } from \"../destination/verifiedDestination\";\nimport { rebindReHomedContainer } from \"../heldContainerRebind\";\nimport {\n moveIndexedContainerChild,\n removeIndexedContainerChild,\n} from \"../lanes/childIndex\";\nimport {\n reconcileLocalOnlyRootContainers,\n reconcileLocalOnlySystemContainers,\n} from \"../reconciliation\";\nimport type {\n ContainerChildIndex,\n ContainerState,\n RemoteContainer,\n RemoteContainerHydrationHost,\n RemoteContainerHydrationState,\n} from \"../types\";\nimport { insertRemoteContainerState } from \"./insertRemoteContainer\";\nimport {\n applyRemoteContainerTimestamps,\n remoteContainerHydrationSaveOptions,\n resolveRemoteContainerHydrationLocalUpdatedAt,\n} from \"./remoteContainerTimestamps\";\n\n// Container ids (restricted to the inbound page) that carry an unsynced local\n// create or move intent. Such a container's parent and local-edit timestamp are\n// owned by its structural-intent lane until that lane reconciles, so inbound\n// hydration must not revert parentId to the server value nor collapse\n// localUpdatedAt — doing so silently undoes a queued move and falsely reads\n// \"synced\". Move intents are read via listUnsyncedMoveIntents so a blocked\n// move — one whose destination parent has not synced yet, the common boot-time\n// case — is protected too. Pending *metadata*\n// updates are handled separately (listRemoteContainerIdsWithPendingMetadataUpdates);\n// structural intents live in the local outbox's container lanes, which that\n// query does not cover.\nexport async function listRemoteContainerIdsWithPendingStructuralIntents(input: {\n remoteContainers: ReadonlyArray<RemoteContainer>;\n state: RemoteContainerHydrationState;\n}): Promise<Set<string>> {\n if (input.remoteContainers.length === 0) {\n return new Set();\n }\n const remoteContainerIds = new Set(\n input.remoteContainers.map((remoteContainer) => remoteContainer.id),\n );\n\n const execSql = input.state.runtime.infra.execSql;\n const [pendingCreateIntents, unsyncedMoveIntents] = await Promise.all([\n input.state.persistence.listPendingCreateIntents(execSql),\n input.state.persistence.listUnsyncedMoveIntents(execSql),\n ]);\n const containerIdsWithPendingStructuralIntents = new Set<string>();\n for (const intent of pendingCreateIntents) {\n if (remoteContainerIds.has(intent.containerId)) {\n containerIdsWithPendingStructuralIntents.add(intent.containerId);\n }\n }\n for (const intent of unsyncedMoveIntents) {\n if (remoteContainerIds.has(intent.containerId)) {\n containerIdsWithPendingStructuralIntents.add(intent.containerId);\n }\n }\n return containerIdsWithPendingStructuralIntents;\n}\n\nexport async function listRemoteContainerIdsWithPendingMetadataUpdates(input: {\n remoteContainers: ReadonlyArray<RemoteContainer>;\n state: RemoteContainerHydrationState;\n}): Promise<Set<string>> {\n const containerIds = input.remoteContainers.flatMap((remoteContainer) => {\n const previousLocalUpdatedAt = input.state.containersById.get(\n remoteContainer.id,\n )?.container.localUpdatedAt;\n\n return previousLocalUpdatedAt &&\n previousLocalUpdatedAt.localeCompare(remoteContainer.updatedAt) > 0\n ? [remoteContainer.id]\n : [];\n });\n if (containerIds.length === 0) {\n return new Set();\n }\n\n const execSql = input.state.runtime.infra.execSql;\n return new Set(\n await input.state.persistence.listContainerIdsWithPendingUpdates(\n execSql,\n containerIds,\n ),\n );\n}\n\nfunction removeMissingHydratedContainer(input: {\n childIdsByParentId?: ContainerChildIndex | undefined;\n existingState: ContainerState;\n host: RemoteContainerHydrationHost;\n previousParentId: string | null;\n remoteContainer: RemoteContainer;\n state: RemoteContainerHydrationState;\n}): void {\n const { existingState, remoteContainer, state } = input;\n if (state.containersById.get(remoteContainer.id) !== existingState) {\n return;\n }\n\n state.containersById.delete(remoteContainer.id);\n if (input.childIdsByParentId) {\n removeIndexedContainerChild(\n input.childIdsByParentId,\n remoteContainer.id,\n input.previousParentId,\n );\n input.childIdsByParentId.delete(remoteContainer.id);\n }\n input.host.updateSnapshot();\n}\n\nfunction createUpdatedRemoteContainerState(\n existingState: ContainerState,\n remoteContainer: RemoteContainer,\n): ContainerState {\n return {\n ...existingState,\n container: applyRemoteContainerTimestamps(\n existingState.container,\n remoteContainer,\n ),\n containerWriterProjection: null,\n metadataReferencedPrincipals: remoteContainer.metadataReferencedPrincipals,\n };\n}\n\nfunction installUpdatedRemoteContainerState(\n existingState: ContainerState,\n nextState: ContainerState,\n host: RemoteContainerHydrationHost,\n): void {\n const wasLocalOnly = existingState.container.serverCreatedAt == null;\n existingState.container = nextState.container;\n existingState.containerWriterProjection = nextState.containerWriterProjection;\n existingState.metadataReferencedPrincipals =\n nextState.metadataReferencedPrincipals;\n // The snapshot copied the live generation when it was taken; a hint that\n // landed during the persist leaves the live metadata projection dropped.\n if (projectionGeneration(existingState) === projectionGeneration(nextState))\n existingState.metadataWriterProjection = nextState.metadataWriterProjection;\n installContainerMetadataRecord(existingState, nextState.record);\n // A document pass may have deferred while this root was awaiting proof.\n // The first durable remote acknowledgement makes its pending creates runnable.\n if (wasLocalOnly && existingState.container.serverCreatedAt != null)\n host.requestDocumentPriming?.();\n}\n\nasync function updateExistingRemoteContainerState(input: {\n childIdsByParentId?: ContainerChildIndex | undefined;\n containerIdsWithPendingMetadataUpdates: ReadonlySet<string>;\n containerIdsWithPendingStructuralIntents: ReadonlySet<string>;\n host: RemoteContainerHydrationHost;\n isCurrent?: (() => boolean) | undefined;\n existingState: ContainerState;\n remoteContainer: RemoteContainer;\n state: RemoteContainerHydrationState;\n}): Promise<ContainerState | null> {\n const { childIdsByParentId, existingState, host, remoteContainer, state } =\n input;\n const previousParentId = existingState.container.parentId;\n const previousLocalUpdatedAt = existingState.container.localUpdatedAt;\n // A queued, not-yet-synced local move/create owns this container's parent\n // until its intent lane reconciles. Omit the remote parent when the page scan\n // saw one; persistence also rechecks the intent transactionally so a move\n // created after that scan wins. Both paths preserve the durable local clock.\n const hasPendingStructuralIntent =\n input.containerIdsWithPendingStructuralIntents.has(remoteContainer.id);\n const nextParentId = hasPendingStructuralIntent\n ? undefined\n : remoteContainer.parentId;\n const localUpdatedAt = resolveRemoteContainerHydrationLocalUpdatedAt({\n containerIdsWithPendingMetadataUpdates:\n input.containerIdsWithPendingMetadataUpdates,\n hasPendingStructuralIntent,\n previousLocalUpdatedAt,\n remoteContainer,\n });\n const nextState = createUpdatedRemoteContainerState(\n existingState,\n remoteContainer,\n );\n const persistenceResult = await host.persistContainerState(\n nextState,\n {\n accessEpoch: remoteContainer.metadataAccessEpoch,\n accessStateHash: remoteContainer.metadataAccessStateHash,\n documentId: remoteContainer.metadataDocumentId,\n effectiveAccessLevel: remoteContainer.effectiveAccessLevel,\n metadataDocumentId: remoteContainer.metadataDocumentId,\n systemSlot: remoteContainer.systemSlot ?? null,\n organizationId: remoteContainer.organizationId,\n ...(nextParentId !== undefined ? { parentId: nextParentId } : {}),\n },\n false,\n remoteContainerHydrationSaveOptions({\n localUpdatedAt,\n remoteContainer,\n }),\n {\n preserveDurableStructureWhenPending: true,\n },\n );\n if (persistenceResult.status === \"missing\") {\n // Hydration persists a detached candidate so an in-flight response cannot\n // mutate mapped state before its guards settle. Retire the exact state this\n // hydration read because store-level reference cleanup cannot see the clone.\n removeMissingHydratedContainer({\n childIdsByParentId,\n existingState,\n host,\n previousParentId,\n remoteContainer,\n state,\n });\n return null;\n }\n if (persistenceResult.status !== \"persisted\") return null;\n const { record: nextRecord } = persistenceResult;\n installContainerMetadataRecord(nextState, nextRecord);\n if (input.isCurrent?.() === false) {\n return existingState;\n }\n nextState.container = {\n ...nextState.container,\n metadataDocumentId: remoteContainer.metadataDocumentId,\n organizationId: remoteContainer.organizationId,\n parentId: nextState.container.parentId,\n };\n\n installUpdatedRemoteContainerState(existingState, nextState, host);\n moveIndexedContainerChild(\n childIdsByParentId,\n remoteContainer.id,\n previousParentId,\n existingState.container.parentId,\n );\n await reconcileLocalOnlyRootContainers({\n childIdsByParentId,\n isCurrent: input.isCurrent,\n remoteRootState: existingState,\n requestDocumentPriming: host.requestDocumentPriming,\n state,\n });\n return existingState;\n}\n\ninterface UpsertRemoteContainerStateInput {\n childIdsByParentId?: ContainerChildIndex | undefined;\n containerIdsWithPendingMetadataUpdates: ReadonlySet<string>;\n containerIdsWithPendingStructuralIntents: ReadonlySet<string>;\n host: RemoteContainerHydrationHost;\n expectedHydrationTombstone?: ContainerHydrationTombstone | null | undefined;\n isCurrent?: (() => boolean) | undefined;\n prefetchedProjection?: PrefetchedDestinationProjection | undefined;\n remoteContainer: RemoteContainer;\n state: RemoteContainerHydrationState;\n}\n\n/** A pull can observe the same manifest change as a missed realtime hint. */\nfunction invalidateListedProjectionCaches(\n input: UpsertRemoteContainerStateInput,\n) {\n const held = input.state.containersById.get(input.remoteContainer.id);\n if (\n held &&\n (held.record.accessStateHash !==\n input.remoteContainer.metadataAccessStateHash ||\n (held.container.effectiveAccessLevel ?? \"read\") !==\n input.remoteContainer.effectiveAccessLevel)\n ) {\n // Eviction admits no remote state. Drop citations before verification can\n // advance local checkpoints past those cached container/document paths.\n invalidateContainerProjectionCaches(input.state, [\n input.remoteContainer.id,\n ]);\n }\n}\n\nexport async function upsertRemoteContainerState(\n input: UpsertRemoteContainerStateInput,\n): Promise<ContainerState | null> {\n let expectedPlacementCheckpoint: AccessManifestCheckpoint | undefined;\n const heldState = input.state.containersById.get(input.remoteContainer.id);\n const heldBinding = heldState\n ? heldContainerBinding(heldState)\n : await input.state.persistence.loadHeldContainerBinding(\n input.state.runtime.infra.execSql,\n input.remoteContainer.id,\n );\n const listed = input.remoteContainer;\n // A folder re-homed away from the listed organization never returns there,\n // whatever binding (possibly none, after a reset) the device holds now.\n const supersededListing =\n !listingRepeatsHeldOrdinaryBinding(heldBinding, listed) &&\n (await input.state.persistence.isSupersededContainerBinding(\n input.state.runtime.infra.execSql,\n { containerId: listed.id, organizationId: listed.organizationId },\n ));\n if (input.isCurrent?.() === false) return null;\n invalidateListedProjectionCaches(input);\n const verified = await verifyRemoteContainerDestination({\n ...input,\n heldBinding,\n supersededListing,\n refresh: !!input.expectedHydrationTombstone,\n onVerifiedCheckpoint: (checkpoint) => {\n expectedPlacementCheckpoint = checkpoint;\n },\n });\n if (!verified || input.isCurrent?.() === false) return null;\n input = { ...input, remoteContainer: verified };\n const existingState = input.state.containersById.get(\n input.remoteContainer.id,\n );\n const remoteState = await runWithSecurityIncidentReporting(\n input.state.runtime.util.reportSecurityIncident,\n {\n objectId: verified.id,\n objectKind: \"container\",\n operation: \"container.binding.persist\",\n organizationId: verified.organizationId,\n },\n async () =>\n (await rebindReHomedContainer({\n existingState,\n heldBinding,\n isCurrent: input.isCurrent,\n state: input.state,\n verified,\n }))\n ? persistVerifiedRemoteContainer({\n ...input,\n existingState,\n expectedPlacementCheckpoint,\n })\n : null,\n );\n if (!remoteState) {\n return null;\n }\n await reconcileLocalOnlySystemContainers({\n childIdsByParentId: input.childIdsByParentId,\n isCurrent: input.isCurrent,\n requestDocumentPriming: input.host.requestDocumentPriming,\n remoteSystemState: remoteState,\n state: input.state,\n });\n return remoteState;\n}\n\n/**\n * Upsert one listed folder, isolating a rejected proof or binding. The refusal\n * was already reported; returning no state leaves the item unapplied without\n * blocking independent folders.\n */\nexport async function upsertIsolatedRemoteContainerState(\n input: UpsertRemoteContainerStateInput,\n): Promise<ContainerState | null> {\n try {\n return await upsertRemoteContainerState(input);\n } catch (error) {\n if (\n !isKeyingVerificationError(error) &&\n !isProjectionVerificationCancelledError(error)\n )\n throw error;\n return null;\n }\n}\n\nfunction persistVerifiedRemoteContainer(\n input: UpsertRemoteContainerStateInput & {\n existingState: ContainerState | undefined;\n expectedPlacementCheckpoint: AccessManifestCheckpoint | undefined;\n },\n): Promise<ContainerState | null> {\n const { existingState, expectedPlacementCheckpoint } = input;\n return existingState\n ? updateExistingRemoteContainerState({\n childIdsByParentId: input.childIdsByParentId,\n containerIdsWithPendingMetadataUpdates:\n input.containerIdsWithPendingMetadataUpdates,\n containerIdsWithPendingStructuralIntents:\n input.containerIdsWithPendingStructuralIntents,\n existingState,\n host: input.host,\n isCurrent: input.isCurrent,\n remoteContainer: input.remoteContainer,\n state: input.state,\n })\n : insertRemoteContainerState({\n childIdsByParentId: input.childIdsByParentId,\n host: input.host,\n expectedHydrationTombstone: input.expectedHydrationTombstone ?? null,\n expectedPlacementCheckpoint,\n isCurrent: input.isCurrent,\n remoteContainer: input.remoteContainer,\n state: input.state,\n });\n}\n"]}
@@ -18,7 +18,7 @@ export type RemoteContainer = Pick<ContainerSummary, "createdAt" | "effectiveAcc
18
18
  export interface ContainerState {
19
19
  containerWriterProjection?: ContainerWriterProjectionResponse | null | undefined;
20
20
  /**
21
- * Bumped whenever a realtime hint drops this container's cached writer
21
+ * Bumped whenever a realtime hint or a pull drops this container's cached writer
22
22
  * projections (container and metadata document alike); an operation that
23
23
  * fetched or carried a projection installs it only if this is unchanged
24
24
  * since it started, so a pre-hint answer never lands after the hint.
@@ -35,6 +35,7 @@ export interface ContainerState {
35
35
  interface RemoteContainerHydrationApi extends Partial<PrincipalPolicyHistoryReader>, CurrentPrincipalPolicyReader {
36
36
  getContainerReplacementAuthorizationsResult(containerId: string, replacesOrganizationId: string): Promise<RequestResult<ContainerReplacementAuthorizationsResponse>>;
37
37
  evictContainerWriterProjection(containerId: string): void;
38
+ evictWriterProjectionsCiting(containerIds: readonly string[]): void;
38
39
  getContainerWriterProjection(containerId: string): Promise<ContainerWriterProjectionResponse | null>;
39
40
  listContainerParentLanes(input: ListContainerParentLanesRequest): Promise<ListContainerParentLanesResponse | null>;
40
41
  }