@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.
- package/client/containerContents/documentDiscovery.js +2 -2
- package/client/containerContents/documentDiscovery.js.map +1 -1
- package/data/containers/shared/types.d.ts +2 -0
- package/data/containers/shared/types.js.map +1 -1
- package/data/persistence/container-contents/containerAccessDiscovery.js +28 -0
- package/data/persistence/container-contents/containerAccessDiscovery.js.map +1 -0
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js +22 -17
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
- package/data/persistence/documents/documentDiscoveryEvidencePersistence.d.ts +1 -0
- package/data/persistence/documents/documentDiscoveryEvidencePersistence.js +1 -0
- package/data/persistence/documents/documentDiscoveryEvidencePersistence.js.map +1 -1
- package/package.json +1 -1
- package/stores/container-contents/remoteEvents/remoteEventProjectionInvalidation.js +3 -33
- package/stores/container-contents/remoteEvents/remoteEventProjectionInvalidation.js.map +1 -1
- package/workflows/container-contents/container-state/projectionCache.js +34 -0
- package/workflows/container-contents/container-state/projectionCache.js.map +1 -1
- package/workflows/container-contents/container-state/types.js.map +1 -1
- package/workflows/container-contents/documentDiscovery/documentDiscoveryApply.js +6 -3
- package/workflows/container-contents/documentDiscovery/documentDiscoveryApply.js.map +1 -1
- package/workflows/container-contents/documentDiscovery/documentDiscoveryTypes.d.ts +4 -3
- package/workflows/container-contents/documentDiscovery/documentDiscoveryTypes.js.map +1 -1
- package/workflows/container-contents/documentDiscovery/index.js +15 -9
- package/workflows/container-contents/documentDiscovery/index.js.map +1 -1
- package/workflows/container-contents/remoteHydration/remoteContainers/remoteContainerState.js +17 -0
- package/workflows/container-contents/remoteHydration/remoteContainers/remoteContainerState.js.map +1 -1
- package/workflows/container-contents/remoteHydration/types.d.ts +2 -1
- package/workflows/container-contents/remoteHydration/types.js.map +1 -1
- package/workflows/container-contents/syncPause/discoveryGuard.js +12 -11
- package/workflows/container-contents/syncPause/discoveryGuard.js.map +1 -1
- package/workflows/containers/child/revoke.d.ts +7 -3
- package/workflows/containers/child/revoke.js +73 -17
- package/workflows/containers/child/revoke.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["import type { RequestResult } from \"@tearleads/api-client\";\nimport type { ContainerReplacementAuthorizationsResponse } from \"@tearleads/validators/operation\";\nimport type { ListContainerParentLanesRequest } from \"@tearleads/validators/request\";\nimport type {\n ContainerSummary,\n ContainerWriterProjectionResponse,\n DocumentWriterProjectionResponse,\n ListContainerParentLanesResponse,\n ListContainersResponse,\n ReferencedPrincipalStateResponse,\n} from \"@tearleads/validators/response\";\nimport type { CurrentPrincipalPolicyReader } from \"../../principals/currentPrincipalPolicyReader\";\nimport type { PrincipalPolicyHistoryReader } from \"../../principals/principalPolicyHistoryReader\";\nimport type {\n ContainerContentsPersistence,\n ContainerDocumentRecord,\n ContainerHydrationTombstone,\n ContainerRecord,\n createContainerParentSyncLane,\n} from \"../containerPersistence\";\nimport type { ContainerMetadataPatch } from \"../metadata\";\nimport type { createContainerMetadataDocument } from \"../metadata/containerMetadataDocument\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\n\nexport type ListedRemoteContainerPageItem =\n ListContainersResponse[\"items\"][number];\nexport type ContainerChildIndex = Map<string, Set<string>>;\nexport type QueueContainerParentLane = (parentId: string | null) => void;\nexport type RemoteContainerIngestQueue = Map<string, RemoteContainer>;\nexport type SaveContainerOptions = Parameters<\n ContainerContentsPersistence[\"saveContainer\"]\n>[3];\n\nexport type ContainerMetadataDocumentState = Awaited<\n ReturnType<typeof createContainerMetadataDocument>\n>;\nexport type RemoteContainer = Pick<\n ContainerSummary,\n | \"createdAt\"\n | \"effectiveAccessLevel\"\n | \"systemSlot\"\n | \"id\"\n | \"metadataAccessEpoch\"\n | \"metadataAccessStateHash\"\n | \"metadataDocumentId\"\n | \"metadataReferencedPrincipals\"\n | \"organizationId\"\n | \"parentId\"\n | \"updatedAt\"\n>;\n\nexport interface ContainerState {\n containerWriterProjection?:\n | ContainerWriterProjectionResponse\n | null\n | undefined;\n /**\n * Bumped whenever a realtime hint drops this container's cached writer\n * projections (container and metadata document alike); an operation that\n * fetched or carried a projection installs it only if this is unchanged\n * since it started, so a pre-hint answer never lands after the hint.\n */\n writerProjectionGeneration?: number | undefined;\n metadataReferencedPrincipals?:\n | readonly ReferencedPrincipalStateResponse[]\n | undefined;\n container: ContainerRecord;\n doc: ContainerMetadataDocumentState;\n metadataWriterProjection?:\n | DocumentWriterProjectionResponse\n | null\n | undefined;\n /** Durable pull progress mirrored from the metadata document record. */\n pullContinuation?: ContainerDocumentRecord[\"pullContinuation\"] | undefined;\n record: ContainerDocumentRecord;\n}\n\ninterface RemoteContainerHydrationApi\n extends Partial<PrincipalPolicyHistoryReader>,\n CurrentPrincipalPolicyReader {\n getContainerReplacementAuthorizationsResult(\n containerId: string,\n replacesOrganizationId: string,\n ): Promise<RequestResult<ContainerReplacementAuthorizationsResponse>>;\n evictContainerWriterProjection(containerId: string): void;\n getContainerWriterProjection(\n containerId: string,\n ): Promise<ContainerWriterProjectionResponse | null>;\n listContainerParentLanes(\n input: ListContainerParentLanesRequest,\n ): Promise<ListContainerParentLanesResponse | null>;\n}\n\ninterface RemoteContainerHydrationRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"adoptRootContainer\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n apiClient: RemoteContainerHydrationApi;\n}\n\nexport interface RemoteContainerHydrationState {\n containersById: Map<string, ContainerState>;\n lifecycleGeneration?: number | undefined;\n persistence: ContainerContentsPersistence;\n rootLaneHydrated?: boolean | undefined;\n runtime: RemoteContainerHydrationRuntime;\n structuralGeneration?: number | undefined;\n}\n\nexport type PersistContainerStateResult =\n | { status: \"identity-superseded\"; record: ContainerDocumentRecord }\n | { status: \"missing\" }\n | {\n status: \"persisted\";\n record: ContainerDocumentRecord;\n createIntentSettled?: true | undefined;\n moveIntentSettled?: true | undefined;\n }\n | { status: \"stale-generation\" };\n\nexport interface RemoteContainerHydrationHost {\n persistContainerState: (\n containerState: ContainerState,\n patch?: Partial<ContainerMetadataPatch>,\n updateView?: boolean,\n saveOptions?: SaveContainerOptions,\n mutationOptions?: {\n /** Queued metadata rows the server committed, settled in this write. */\n acceptedPendingUpdateIds?: readonly string[] | undefined;\n clearSyncFailure?: boolean | undefined;\n expectedStateWhenMissing?: ContainerState | undefined;\n isCurrent?: (() => boolean) | undefined;\n createIntentSettlement?: Parameters<\n ContainerContentsPersistence[\"commitMetadataMutation\"]\n >[1][\"createIntentSettlement\"];\n moveIntentSettlement?: Parameters<\n ContainerContentsPersistence[\"commitMetadataMutation\"]\n >[1][\"moveIntentSettlement\"];\n preserveDurableStructureWhenPending?: boolean | undefined;\n },\n ) => Promise<PersistContainerStateResult>;\n requestDocumentPriming?: (() => void) | undefined;\n updateSnapshot: () => void;\n}\n\nexport interface ContainerParentHydrationLane {\n parentId: string | null;\n watermark?: ListContainersResponse[\"nextWatermark\"];\n}\n\nexport interface ExpectedContainerState {\n container: ContainerRecord;\n fingerprint: string;\n}\n\nexport interface FetchedContainerParentLanePage {\n expectedContainerStates: ReadonlyMap<string, ExpectedContainerState>;\n expectedHydrationTombstones: ReadonlyMap<string, ContainerHydrationTombstone>;\n lane: ContainerParentHydrationLane;\n response: ListContainersResponse;\n syncLane: ReturnType<typeof createContainerParentSyncLane>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["import type { RequestResult } from \"@tearleads/api-client\";\nimport type { ContainerReplacementAuthorizationsResponse } from \"@tearleads/validators/operation\";\nimport type { ListContainerParentLanesRequest } from \"@tearleads/validators/request\";\nimport type {\n ContainerSummary,\n ContainerWriterProjectionResponse,\n DocumentWriterProjectionResponse,\n ListContainerParentLanesResponse,\n ListContainersResponse,\n ReferencedPrincipalStateResponse,\n} from \"@tearleads/validators/response\";\nimport type { CurrentPrincipalPolicyReader } from \"../../principals/currentPrincipalPolicyReader\";\nimport type { PrincipalPolicyHistoryReader } from \"../../principals/principalPolicyHistoryReader\";\nimport type {\n ContainerContentsPersistence,\n ContainerDocumentRecord,\n ContainerHydrationTombstone,\n ContainerRecord,\n createContainerParentSyncLane,\n} from \"../containerPersistence\";\nimport type { ContainerMetadataPatch } from \"../metadata\";\nimport type { createContainerMetadataDocument } from \"../metadata/containerMetadataDocument\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\n\nexport type ListedRemoteContainerPageItem =\n ListContainersResponse[\"items\"][number];\nexport type ContainerChildIndex = Map<string, Set<string>>;\nexport type QueueContainerParentLane = (parentId: string | null) => void;\nexport type RemoteContainerIngestQueue = Map<string, RemoteContainer>;\nexport type SaveContainerOptions = Parameters<\n ContainerContentsPersistence[\"saveContainer\"]\n>[3];\n\nexport type ContainerMetadataDocumentState = Awaited<\n ReturnType<typeof createContainerMetadataDocument>\n>;\nexport type RemoteContainer = Pick<\n ContainerSummary,\n | \"createdAt\"\n | \"effectiveAccessLevel\"\n | \"systemSlot\"\n | \"id\"\n | \"metadataAccessEpoch\"\n | \"metadataAccessStateHash\"\n | \"metadataDocumentId\"\n | \"metadataReferencedPrincipals\"\n | \"organizationId\"\n | \"parentId\"\n | \"updatedAt\"\n>;\n\nexport interface ContainerState {\n containerWriterProjection?:\n | ContainerWriterProjectionResponse\n | null\n | undefined;\n /**\n * Bumped whenever a realtime hint or a pull drops this container's cached writer\n * projections (container and metadata document alike); an operation that\n * fetched or carried a projection installs it only if this is unchanged\n * since it started, so a pre-hint answer never lands after the hint.\n */\n writerProjectionGeneration?: number | undefined;\n metadataReferencedPrincipals?:\n | readonly ReferencedPrincipalStateResponse[]\n | undefined;\n container: ContainerRecord;\n doc: ContainerMetadataDocumentState;\n metadataWriterProjection?:\n | DocumentWriterProjectionResponse\n | null\n | undefined;\n /** Durable pull progress mirrored from the metadata document record. */\n pullContinuation?: ContainerDocumentRecord[\"pullContinuation\"] | undefined;\n record: ContainerDocumentRecord;\n}\n\ninterface RemoteContainerHydrationApi\n extends Partial<PrincipalPolicyHistoryReader>,\n CurrentPrincipalPolicyReader {\n getContainerReplacementAuthorizationsResult(\n containerId: string,\n replacesOrganizationId: string,\n ): Promise<RequestResult<ContainerReplacementAuthorizationsResponse>>;\n evictContainerWriterProjection(containerId: string): void;\n evictWriterProjectionsCiting(containerIds: readonly string[]): void;\n getContainerWriterProjection(\n containerId: string,\n ): Promise<ContainerWriterProjectionResponse | null>;\n listContainerParentLanes(\n input: ListContainerParentLanesRequest,\n ): Promise<ListContainerParentLanesResponse | null>;\n}\n\ninterface RemoteContainerHydrationRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"adoptRootContainer\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n apiClient: RemoteContainerHydrationApi;\n}\n\nexport interface RemoteContainerHydrationState {\n containersById: Map<string, ContainerState>;\n lifecycleGeneration?: number | undefined;\n persistence: ContainerContentsPersistence;\n rootLaneHydrated?: boolean | undefined;\n runtime: RemoteContainerHydrationRuntime;\n structuralGeneration?: number | undefined;\n}\n\nexport type PersistContainerStateResult =\n | { status: \"identity-superseded\"; record: ContainerDocumentRecord }\n | { status: \"missing\" }\n | {\n status: \"persisted\";\n record: ContainerDocumentRecord;\n createIntentSettled?: true | undefined;\n moveIntentSettled?: true | undefined;\n }\n | { status: \"stale-generation\" };\n\nexport interface RemoteContainerHydrationHost {\n persistContainerState: (\n containerState: ContainerState,\n patch?: Partial<ContainerMetadataPatch>,\n updateView?: boolean,\n saveOptions?: SaveContainerOptions,\n mutationOptions?: {\n /** Queued metadata rows the server committed, settled in this write. */\n acceptedPendingUpdateIds?: readonly string[] | undefined;\n clearSyncFailure?: boolean | undefined;\n expectedStateWhenMissing?: ContainerState | undefined;\n isCurrent?: (() => boolean) | undefined;\n createIntentSettlement?: Parameters<\n ContainerContentsPersistence[\"commitMetadataMutation\"]\n >[1][\"createIntentSettlement\"];\n moveIntentSettlement?: Parameters<\n ContainerContentsPersistence[\"commitMetadataMutation\"]\n >[1][\"moveIntentSettlement\"];\n preserveDurableStructureWhenPending?: boolean | undefined;\n },\n ) => Promise<PersistContainerStateResult>;\n requestDocumentPriming?: (() => void) | undefined;\n updateSnapshot: () => void;\n}\n\nexport interface ContainerParentHydrationLane {\n parentId: string | null;\n watermark?: ListContainersResponse[\"nextWatermark\"];\n}\n\nexport interface ExpectedContainerState {\n container: ContainerRecord;\n fingerprint: string;\n}\n\nexport interface FetchedContainerParentLanePage {\n expectedContainerStates: ReadonlyMap<string, ExpectedContainerState>;\n expectedHydrationTombstones: ReadonlyMap<string, ContainerHydrationTombstone>;\n lane: ContainerParentHydrationLane;\n response: ListContainersResponse;\n syncLane: ReturnType<typeof createContainerParentSyncLane>;\n}\n"]}
|
|
@@ -3,22 +3,23 @@ import { sqlDocumentContainerProjectionPersistence } from "../../../data/persist
|
|
|
3
3
|
import { upsertDiscoveredDocuments } from "../../../data/persistence/documents/documentsPersistence.js";
|
|
4
4
|
import { runSerializedSqlMutation } from "../../../data/sqlite/sqlExec.js";
|
|
5
5
|
/**
|
|
6
|
-
* Binds a discovery lane's writes to its
|
|
7
|
-
* unpaused.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* holds. A stale listing therefore never restores documents, links, or the
|
|
6
|
+
* Binds a discovery lane's writes to its generation and its organization staying
|
|
7
|
+
* unpaused. Access changes and pauses invalidate in-flight listings, so the
|
|
8
|
+
* apply stops at its next check. Each write already started rechecks under
|
|
9
|
+
* the SQLite mutation lock held by the reset or pause cleanup. A stale listing therefore never restores documents, links, or the
|
|
11
10
|
* watermark the cleanup removed; a watermark saved after the cleanup would
|
|
12
11
|
* otherwise make the listing after a resume skip every unchanged document.
|
|
13
12
|
*/
|
|
14
|
-
export function
|
|
15
|
-
const
|
|
13
|
+
export function guardDocumentDiscoveryWrites(input) {
|
|
14
|
+
const whileCurrent = (skipped, write, stillCurrent) => runSerializedSqlMutation(input.execSql, async (lockedExecSql) => input.isPaused() || !(await stillCurrent())
|
|
15
|
+
? skipped
|
|
16
|
+
: write(lockedExecSql));
|
|
16
17
|
return {
|
|
17
|
-
replaceDocumentLinksBatch: (inputs) =>
|
|
18
|
-
saveContainerDocumentWatermark: (containerId, watermark) =>
|
|
18
|
+
replaceDocumentLinksBatch: (inputs, stillCurrent) => whileCurrent(undefined, (lockedExecSql) => sqlDocumentContainerProjectionPersistence.replaceDocumentLinksBatch(lockedExecSql, inputs), stillCurrent),
|
|
19
|
+
saveContainerDocumentWatermark: (containerId, watermark, stillCurrent) => whileCurrent(undefined, async (lockedExecSql) => {
|
|
19
20
|
await sqlContainerSyncWatermarkPersistence.saveWatermark(lockedExecSql, containerContentsSyncLane(containerId), watermark);
|
|
20
|
-
}),
|
|
21
|
-
upsertDiscoveredDocuments: (inputs) =>
|
|
21
|
+
}, stillCurrent),
|
|
22
|
+
upsertDiscoveredDocuments: (inputs, stillCurrent) => whileCurrent([], (lockedExecSql) => upsertDiscoveredDocuments(lockedExecSql, inputs), stillCurrent),
|
|
22
23
|
async verifyDiscoveredDocuments(...args) {
|
|
23
24
|
const verification = await input.verifyDiscoveredDocuments(...args);
|
|
24
25
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discoveryGuard.js","sourceRoot":"","sources":["discoveryGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,oCAAoC,GACrC,MAAM,gFAAgF,CAAC;AACxF,OAAO,EAAE,yCAAyC,EAAE,MAAM,qFAAqF,CAAC;AAChJ,OAAO,EAAE,yBAAyB,EAAE,MAAM,0DAA0D,CAAC;AACrG,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAYxE
|
|
1
|
+
{"version":3,"file":"discoveryGuard.js","sourceRoot":"","sources":["discoveryGuard.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,oCAAoC,GACrC,MAAM,gFAAgF,CAAC;AACxF,OAAO,EAAE,yCAAyC,EAAE,MAAM,qFAAqF,CAAC;AAChJ,OAAO,EAAE,yBAAyB,EAAE,MAAM,0DAA0D,CAAC;AACrG,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAYxE;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAAC,KAI5C;IACC,MAAM,YAAY,GAAG,CACnB,OAAU,EACV,KAA6C,EAC7C,YAAoC,EACxB,EAAE,CACd,wBAAwB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,CAC9D,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,MAAM,YAAY,EAAE,CAAC;QACzC,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CACzB,CAAC;IACJ,OAAO;QACL,yBAAyB,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,CAClD,YAAY,CACV,SAAS,EACT,CAAC,aAAa,EAAE,EAAE,CAChB,yCAAyC,CAAC,yBAAyB,CACjE,aAAa,EACb,MAAM,CACP,EACH,YAAY,CACb;QACH,8BAA8B,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,CACvE,YAAY,CACV,SAAS,EACT,KAAK,EAAE,aAAa,EAAE,EAAE;YACtB,MAAM,oCAAoC,CAAC,aAAa,CACtD,aAAa,EACb,yBAAyB,CAAC,WAAW,CAAC,EACtC,SAAS,CACV,CAAC;QACJ,CAAC,EACD,YAAY,CACb;QACH,yBAAyB,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,CAClD,YAAY,CACV,EAAE,EACF,CAAC,aAAa,EAAE,EAAE,CAAC,yBAAyB,CAAC,aAAa,EAAE,MAAM,CAAC,EACnE,YAAY,CACb;QACH,KAAK,CAAC,yBAAyB,CAAC,GAAG,IAAI;YACrC,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,yBAAyB,CAAC,GAAG,IAAI,CAAC,CAAC;YACpE,OAAO;gBACL,GAAG,YAAY;gBACf,SAAS,EAAE,KAAK,IAAI,EAAE,CACpB,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,YAAY,CAAC,SAAS,EAAE,CAAC;aACxD,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import {\n containerContentsSyncLane,\n sqlContainerSyncWatermarkPersistence,\n} from \"../../../data/persistence/container-contents/containerSyncWatermarkPersistence\";\nimport { sqlDocumentContainerProjectionPersistence } from \"../../../data/persistence/container-contents/documentContainerProjectionPersistence\";\nimport { upsertDiscoveredDocuments } from \"../../../data/persistence/documents/documentsPersistence\";\nimport { runSerializedSqlMutation } from \"../../../data/sqlite/sqlExec\";\nimport type { ExecSql } from \"../../../data/sqlite/sqlSchema\";\nimport type { DiscoverContainerDocumentsOptions } from \"../documentDiscovery/documentDiscoveryTypes\";\n\ntype PauseGuardedDiscovery = Pick<\n DiscoverContainerDocumentsOptions,\n | \"replaceDocumentLinksBatch\"\n | \"saveContainerDocumentWatermark\"\n | \"upsertDiscoveredDocuments\"\n | \"verifyDiscoveredDocuments\"\n>;\n\n/**\n * Binds a discovery lane's writes to its generation and its organization staying\n * unpaused. Access changes and pauses invalidate in-flight listings, so the\n * apply stops at its next check. Each write already started rechecks under\n * the SQLite mutation lock held by the reset or pause cleanup. A stale listing therefore never restores documents, links, or the\n * watermark the cleanup removed; a watermark saved after the cleanup would\n * otherwise make the listing after a resume skip every unchanged document.\n */\nexport function guardDocumentDiscoveryWrites(input: {\n readonly execSql: ExecSql;\n readonly isPaused: () => boolean;\n readonly verifyDiscoveredDocuments: DiscoverContainerDocumentsOptions[\"verifyDiscoveredDocuments\"];\n}): PauseGuardedDiscovery {\n const whileCurrent = <T>(\n skipped: T,\n write: (lockedExecSql: ExecSql) => Promise<T>,\n stillCurrent: () => Promise<boolean>,\n ): Promise<T> =>\n runSerializedSqlMutation(input.execSql, async (lockedExecSql) =>\n input.isPaused() || !(await stillCurrent())\n ? skipped\n : write(lockedExecSql),\n );\n return {\n replaceDocumentLinksBatch: (inputs, stillCurrent) =>\n whileCurrent(\n undefined,\n (lockedExecSql) =>\n sqlDocumentContainerProjectionPersistence.replaceDocumentLinksBatch(\n lockedExecSql,\n inputs,\n ),\n stillCurrent,\n ),\n saveContainerDocumentWatermark: (containerId, watermark, stillCurrent) =>\n whileCurrent(\n undefined,\n async (lockedExecSql) => {\n await sqlContainerSyncWatermarkPersistence.saveWatermark(\n lockedExecSql,\n containerContentsSyncLane(containerId),\n watermark,\n );\n },\n stillCurrent,\n ),\n upsertDiscoveredDocuments: (inputs, stillCurrent) =>\n whileCurrent(\n [],\n (lockedExecSql) => upsertDiscoveredDocuments(lockedExecSql, inputs),\n stillCurrent,\n ),\n async verifyDiscoveredDocuments(...args) {\n const verification = await input.verifyDiscoveredDocuments(...args);\n return {\n ...verification,\n isCurrent: async () =>\n !input.isPaused() && (await verification.isCurrent()),\n };\n },\n };\n}\n"]}
|
|
@@ -24,7 +24,7 @@ export declare function buildMaterializedContainerRevokePlan(input: {
|
|
|
24
24
|
targetSecretKey: Uint8Array;
|
|
25
25
|
warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;
|
|
26
26
|
}): Promise<MaterializedContainerRevokePlan>;
|
|
27
|
-
|
|
27
|
+
interface RevokeRemoteContainerInput {
|
|
28
28
|
reportSecurityIncident: SecurityIncidentReporter;
|
|
29
29
|
apiClient: ContainerRevokeApi;
|
|
30
30
|
author: ContainerMutationAuthor;
|
|
@@ -37,8 +37,12 @@ export declare function revokeRemoteContainer(input: {
|
|
|
37
37
|
stillCurrent?: (() => boolean) | undefined;
|
|
38
38
|
targetSecretKey: Uint8Array;
|
|
39
39
|
warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;
|
|
40
|
-
}
|
|
40
|
+
}
|
|
41
|
+
type RevokedRemoteContainer = {
|
|
41
42
|
containerKey: Uint8Array;
|
|
42
43
|
plan: ContainerRevokePlan;
|
|
43
44
|
response: ContainerRotationResponse;
|
|
44
|
-
}
|
|
45
|
+
};
|
|
46
|
+
/** A concurrent mutation can advance the path while a revoke is in flight. */
|
|
47
|
+
export declare function revokeRemoteContainer(input: RevokeRemoteContainerInput): Promise<RevokedRemoteContainer | null>;
|
|
48
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { computeContainerKekKeyringHash, computeContainerKekPredecessorBridgeHash, deriveContainerKekWrappingPublicKey, } from "../../../internal/crypto/index.js";
|
|
1
|
+
import { computeContainerKekKeyringHash, computeContainerKekPredecessorBridgeHash, deriveContainerKekWrappingPublicKey, KeyingVerificationError, } from "../../../internal/crypto/index.js";
|
|
2
2
|
import { assertContainerAuthorAccess } from "../../../data/containers/shared/authorAccess.js";
|
|
3
3
|
import { buildContainerCreateKeyEpoch, signContainerMutationEvent, } from "../../../data/containers/shared/events.js";
|
|
4
|
+
import { isStaleParentContainerPathFailure } from "../../../data/containers/shared/mutationFailures.js";
|
|
4
5
|
import { asContainerManifestBundle, uniqueSortedManifestHashes, } from "../../../data/containers/shared/projection.js";
|
|
5
6
|
import { readCanonicalRecord } from "../../../data/keying/canonicalJson.js";
|
|
6
7
|
import { requireProjectionUserKeyResolver, } from "../../../data/keyingProjectionVerification/index.js";
|
|
@@ -162,7 +163,23 @@ export async function buildMaterializedContainerRevokePlan(input) {
|
|
|
162
163
|
wraps,
|
|
163
164
|
});
|
|
164
165
|
}
|
|
165
|
-
|
|
166
|
+
const STALE_PATH = Symbol("stale-revoke-path");
|
|
167
|
+
async function submitRevocation(input, plan, carried) {
|
|
168
|
+
const request = carried.length
|
|
169
|
+
? { ...plan.request, containerRekeys: [...carried] }
|
|
170
|
+
: plan.request;
|
|
171
|
+
const options = {
|
|
172
|
+
expectedPaymentRequiredOrganizationId: input.author.organizationId,
|
|
173
|
+
};
|
|
174
|
+
const { revokeContainerResult } = input.apiClient;
|
|
175
|
+
return submitContainerRotation({
|
|
176
|
+
plain: () => input.apiClient.revokeContainer(input.containerId, request, options),
|
|
177
|
+
result: revokeContainerResult
|
|
178
|
+
? () => revokeContainerResult.call(input.apiClient, input.containerId, request, { ...options, reportErrors: false })
|
|
179
|
+
: undefined,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
async function planRevoke(input) {
|
|
166
183
|
const previousProjection = await input.apiClient.getContainerWriterProjection(input.containerId);
|
|
167
184
|
if (!previousProjection) {
|
|
168
185
|
return null;
|
|
@@ -179,7 +196,16 @@ export async function revokeRemoteContainer(input) {
|
|
|
179
196
|
targetSecretKey: input.targetSecretKey,
|
|
180
197
|
warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,
|
|
181
198
|
});
|
|
182
|
-
return
|
|
199
|
+
return { previousProjection, materializedPlan };
|
|
200
|
+
}
|
|
201
|
+
async function revokeRemoteContainerOnce(input, retriesStalePath, onAccepted) {
|
|
202
|
+
const planned = await planRevoke(input);
|
|
203
|
+
if (!planned)
|
|
204
|
+
return null;
|
|
205
|
+
const { previousProjection, materializedPlan } = planned;
|
|
206
|
+
let stalePath = false;
|
|
207
|
+
const revoked = await submitAcknowledgedContainerMutation({
|
|
208
|
+
onAccepted,
|
|
183
209
|
reportSecurityIncident: input.reportSecurityIncident,
|
|
184
210
|
recitationPolicies: [],
|
|
185
211
|
apiClient: input.apiClient,
|
|
@@ -209,21 +235,51 @@ export async function revokeRemoteContainer(input) {
|
|
|
209
235
|
execSql: input.execSql,
|
|
210
236
|
plan: materializedPlan.plan,
|
|
211
237
|
stillCurrent: input.stillCurrent,
|
|
212
|
-
submit: (carried) => {
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
? () => revokeContainerResult.call(input.apiClient, input.containerId, request, { ...options, reportErrors: false })
|
|
224
|
-
: undefined,
|
|
225
|
-
});
|
|
238
|
+
submit: async (carried) => {
|
|
239
|
+
const result = await submitRevocation(input, materializedPlan.plan, carried);
|
|
240
|
+
if (!result.ok &&
|
|
241
|
+
retriesStalePath &&
|
|
242
|
+
isStaleParentContainerPathFailure(result)) {
|
|
243
|
+
stalePath = true;
|
|
244
|
+
// The refused transaction rolled back. Replan it without reporting an
|
|
245
|
+
// error that the caller can repair; a second refusal is terminal.
|
|
246
|
+
return { ok: false, status: result.status, code: result.code };
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
226
249
|
},
|
|
227
250
|
});
|
|
251
|
+
return revoked ?? (stalePath ? STALE_PATH : null);
|
|
252
|
+
}
|
|
253
|
+
async function revokeRemoteContainerAttempt(input, retriesStalePath) {
|
|
254
|
+
let accepted = false;
|
|
255
|
+
try {
|
|
256
|
+
return await revokeRemoteContainerOnce(input, retriesStalePath, () => {
|
|
257
|
+
accepted = true;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
// Our own or a carried descendant's cached projection may trail a pin.
|
|
262
|
+
// Refresh only a refused/unsubmitted attempt, never an accepted rotation.
|
|
263
|
+
if (retriesStalePath &&
|
|
264
|
+
!accepted &&
|
|
265
|
+
error instanceof KeyingVerificationError &&
|
|
266
|
+
error.code === "rollback")
|
|
267
|
+
return STALE_PATH;
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** A concurrent mutation can advance the path while a revoke is in flight. */
|
|
272
|
+
export async function revokeRemoteContainer(input) {
|
|
273
|
+
if (input.stillCurrent?.() === false)
|
|
274
|
+
return null;
|
|
275
|
+
const first = await revokeRemoteContainerAttempt(input, true);
|
|
276
|
+
if (first !== STALE_PATH)
|
|
277
|
+
return first;
|
|
278
|
+
if (input.stillCurrent?.() === false)
|
|
279
|
+
return null;
|
|
280
|
+
input.apiClient.evictContainerWriterProjection?.(input.containerId);
|
|
281
|
+
input.apiClient.evictWriterProjectionsCiting?.([input.containerId]);
|
|
282
|
+
const second = await revokeRemoteContainerAttempt(input, false);
|
|
283
|
+
return second === STALE_PATH ? null : second;
|
|
228
284
|
}
|
|
229
285
|
//# sourceMappingURL=revoke.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revoke.js","sourceRoot":"","sources":["revoke.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccessEvent,\n AccessManifest,\n ContainerAccessManifestState,\n ContainerKekKeyring,\n ContainerKekPredecessorBridge,\n ContainerKeyEpoch,\n ContainerKeyWrap,\n ContainerRevokeAccessEventBody,\n ContainerUserRecipientKey,\n} from \"@tearleads/crypto\";\nimport {\n computeContainerKekKeyringHash,\n computeContainerKekPredecessorBridgeHash,\n deriveContainerKekWrappingPublicKey,\n} from \"@tearleads/crypto\";\nimport type {\n AccessManifestBundleWire,\n ContainerMutationRequest,\n} from \"@tearleads/validators/request\";\nimport type {\n ContainerKekResponse,\n ContainerRotationResponse,\n ContainerWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { assertContainerAuthorAccess } from \"../../../data/containers/shared/authorAccess\";\nimport {\n buildContainerCreateKeyEpoch,\n signContainerMutationEvent,\n} from \"../../../data/containers/shared/events\";\nimport {\n asContainerManifestBundle,\n uniqueSortedManifestHashes,\n} from \"../../../data/containers/shared/projection\";\nimport type {\n ContainerMutationAuthor,\n ContainerRevokeApi,\n ContainerRevokePlan,\n MaterializedContainerRevokePlan,\n} from \"../../../data/containers/shared/types\";\nimport { readCanonicalRecord } from \"../../../data/keying/canonicalJson\";\nimport {\n type PrincipalPolicyCache,\n type ProjectionUserKeyResolver,\n type ReferencedPrincipalPolicyWarmer,\n requireProjectionUserKeyResolver,\n} from \"../../../data/keyingProjectionVerification\";\nimport type { PrincipalPolicyCurrentEvidence } from \"../../../data/principals/principalPolicyEvidence\";\nimport type { SecurityIncidentReporter } from \"../../../data/shared/securityIncidents\";\nimport type { ExecSql } from \"../../../data/sqlite/sqlSchema\";\nimport { buildContainerRotationArtifacts } from \"./move/moveRotation\";\nimport {\n containerMutationRequestCore,\n previousPathRequestFields,\n readCanonicalRecordOrNull,\n} from \"./mutationRequestCore\";\nimport {\n submitAcknowledgedContainerMutation,\n submitContainerRotation,\n} from \"./mutationSubmit\";\nimport { containerWriterProjectionFromRotationPlan } from \"./rekey/rekeyProjection\";\nimport {\n type ContainerRevokeSubject,\n deriveContainerRevokeManifest,\n} from \"./revokeManifest\";\nimport { resolveRotationContext } from \"./rotation/rotationContext\";\nimport { collectContainerRevokePrincipalPolicies } from \"./rotation/rotationPrincipalPolicies\";\nimport { buildContainerRotationWraps } from \"./rotation/rotationWraps\";\n\nfunction buildContainerRevokeRequest(input: {\n body: ContainerRevokeAccessEventBody;\n event: AccessEvent;\n keyEpoch: ContainerKeyEpoch;\n keyring: ContainerKekKeyring;\n manifest: AccessManifest;\n manifestHash: string;\n parentKek: ContainerKekResponse | null;\n predecessorBridge: ContainerKekPredecessorBridge;\n previousManifest: AccessManifestBundleWire;\n previousProjection: ContainerWriterProjectionResponse;\n principalPolicies: readonly PrincipalPolicyCurrentEvidence[];\n userRecipientKeys: readonly ContainerUserRecipientKey[];\n wraps: readonly ContainerKeyWrap[];\n}): ContainerMutationRequest {\n return {\n ...containerMutationRequestCore(\"revoke\", input),\n ...previousPathRequestFields(\n input.previousManifest,\n input.previousProjection,\n ),\n predecessorBridge: readCanonicalRecord(\n input.predecessorBridge,\n \"Container revoke predecessor bridge\",\n ),\n keyring: readCanonicalRecord(input.keyring, \"Container revoke keyring\"),\n parentKekState: readCanonicalRecordOrNull(\n input.parentKek,\n \"Container revoke parent KEK state\",\n ),\n };\n}\n\nfunction buildContainerRevokePlanResult(input: {\n body: ContainerRevokeAccessEventBody;\n containerId: string;\n containerKey: Uint8Array;\n containerKeyEpochId: string;\n event: AccessEvent;\n eventHash: string;\n keyEpoch: ContainerKeyEpoch;\n keyring: ContainerKekKeyring;\n manifest: AccessManifest;\n manifestHash: string;\n parentKek: ContainerKekResponse | null;\n predecessorBridge: ContainerKekPredecessorBridge;\n previousManifest: AccessManifestBundleWire;\n previousProjection: ContainerWriterProjectionResponse;\n principalPolicies: readonly PrincipalPolicyCurrentEvidence[];\n state: ContainerAccessManifestState;\n userRecipientKeys: ContainerUserRecipientKey[];\n wraps: ContainerKeyWrap[];\n}): MaterializedContainerRevokePlan {\n const plan: ContainerRevokePlan = {\n body: input.body,\n containerId: input.containerId,\n containerKeyEpochId: input.containerKeyEpochId,\n event: input.event,\n eventHash: input.eventHash,\n keyEpoch: input.keyEpoch,\n keyring: input.keyring,\n manifest: input.manifest,\n manifestHash: input.manifestHash,\n previousManifest: input.previousManifest,\n request: buildContainerRevokeRequest({\n body: input.body,\n event: input.event,\n keyEpoch: input.keyEpoch,\n keyring: input.keyring,\n manifest: input.manifest,\n manifestHash: input.manifestHash,\n parentKek: input.parentKek,\n predecessorBridge: input.predecessorBridge,\n previousManifest: input.previousManifest,\n previousProjection: input.previousProjection,\n principalPolicies: input.principalPolicies,\n userRecipientKeys: input.userRecipientKeys,\n wraps: input.wraps,\n }),\n state: input.state,\n userRecipientKeys: input.userRecipientKeys,\n wraps: input.wraps,\n };\n\n return { containerKey: input.containerKey, plan };\n}\n\n// biome-ignore lint/complexity/noExcessiveLinesPerFunction: Revoke planning has to keep the manifest, KEK, wraps, and signed event in one auditable transition.\nexport async function buildMaterializedContainerRevokePlan(input: {\n author: ContainerMutationAuthor;\n eventId?: string | undefined;\n execSql: ExecSql;\n /** Keys a batch minted above this container and has yet to commit. */\n knownContainerKeks?: ReadonlyMap<string, Uint8Array> | undefined;\n /** False for a projection re-rooted on rotations not yet acknowledged. */\n persistVerificationCheckpoints?: boolean | undefined;\n previousProjection: ContainerWriterProjectionResponse;\n /** Verified policies the path may cite before they are stored locally. */\n principalPolicyCache?: PrincipalPolicyCache | undefined;\n replacementPrincipalPolicy?: PrincipalPolicyCurrentEvidence | undefined;\n revokedSubject: ContainerRevokeSubject;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n stillCurrent?: (() => boolean) | undefined;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n}): Promise<MaterializedContainerRevokePlan> {\n const resolveProjectionUserKey = requireProjectionUserKeyResolver(\n input.resolveProjectionUserKey,\n \"Remote container revoke\",\n );\n const {\n parentKek,\n parentPublicKey,\n predecessorContainerKey,\n previousState,\n target,\n } = await resolveRotationContext(input, \"revoke\");\n const principalPolicies = await collectContainerRevokePrincipalPolicies({\n execSql: input.execSql,\n persistVerificationCheckpoints: input.persistVerificationCheckpoints,\n previousProjection: input.previousProjection,\n principalPolicyCache: input.principalPolicyCache,\n resolveUserKey: resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n assertContainerAuthorAccess({\n author: input.author,\n projection: input.previousProjection,\n principalPolicies,\n minimumAccess: \"admin\",\n });\n const nextContainerKeyEpoch = target.kek.containerKeyEpoch + 1;\n const { containerKey, containerKeyEpochId, keyring, predecessorBridge } =\n await buildContainerRotationArtifacts({\n containerId: previousState.containerId,\n currentKek: target.kek,\n currentKeyMaterial: predecessorContainerKey,\n keyEpoch: nextContainerKeyEpoch,\n signedEpochIds: target.signedEpochIds,\n });\n const body: ContainerRevokeAccessEventBody = {\n eventType: \"container.revoke\",\n parentManifestHash:\n input.previousProjection.path.at(-2)?.manifestHash ?? null,\n containerKeyPublicKey: await deriveContainerKekWrappingPublicKey({\n containerId: previousState.containerId,\n keyMaterial: containerKey,\n }),\n containerKeyEpochId,\n keyringHash: await computeContainerKekKeyringHash(keyring),\n predecessorBridgeHash:\n await computeContainerKekPredecessorBridgeHash(predecessorBridge),\n subjectId: input.revokedSubject.subjectId,\n subjectType: input.revokedSubject.subjectType,\n };\n const { event, eventHash } = await signContainerMutationEvent({\n author: input.author,\n body,\n containerId: previousState.containerId,\n dependencyManifestHashes: uniqueSortedManifestHashes(\n input.previousProjection.path,\n ),\n eventId: input.eventId ?? crypto.randomUUID(),\n previousManifestHash: target.manifest.manifestHash,\n signedAt: input.signedAt ?? new Date().toISOString(),\n });\n const { manifest, manifestHash, state } = await deriveContainerRevokeManifest(\n {\n containerKeyPublicKey: body.containerKeyPublicKey,\n parentManifestHash: body.parentManifestHash,\n containerKeyEpochId,\n eventHash,\n previousManifest: target.manifest,\n revokedSubject: input.revokedSubject,\n },\n );\n const keyEpoch = buildContainerCreateKeyEpoch({\n containerId: previousState.containerId,\n containerKeyEpochId,\n eventHash,\n keyEpoch: nextContainerKeyEpoch,\n manifestHash,\n parentContainerKeyEpochId: parentKek?.containerKeyEpochId ?? null,\n });\n\n const replacementPrincipalPolicy = input.replacementPrincipalPolicy;\n if (\n replacementPrincipalPolicy &&\n (replacementPrincipalPolicy.principalType !==\n input.revokedSubject.subjectType ||\n replacementPrincipalPolicy.principalId !== input.revokedSubject.subjectId)\n ) {\n throw new Error(\"Container revoke replacement principal does not match\");\n }\n const currentPrincipalPolicies = replacementPrincipalPolicy\n ? principalPolicies.filter(\n (policy) =>\n policy.principalType !== replacementPrincipalPolicy.principalType ||\n policy.principalId !== replacementPrincipalPolicy.principalId,\n )\n : principalPolicies;\n const { userRecipientKeys, wraps } = await buildContainerRotationWraps({\n containerKey,\n containerKeyEpochId,\n manifestHash,\n operationLabel: \"Container revoke\",\n parentKek,\n parentPublicKey,\n principalPolicies: currentPrincipalPolicies,\n resolveUserKey: resolveProjectionUserKey,\n state,\n });\n\n return buildContainerRevokePlanResult({\n body,\n containerId: previousState.containerId,\n containerKey,\n containerKeyEpochId,\n event,\n eventHash,\n keyEpoch,\n keyring,\n manifest,\n manifestHash,\n parentKek,\n predecessorBridge,\n previousManifest: asContainerManifestBundle(target.manifest),\n previousProjection: input.previousProjection,\n principalPolicies: currentPrincipalPolicies,\n state,\n userRecipientKeys,\n wraps,\n });\n}\n\nexport async function revokeRemoteContainer(input: {\n reportSecurityIncident: SecurityIncidentReporter;\n apiClient: ContainerRevokeApi;\n author: ContainerMutationAuthor;\n containerId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n revokedSubject: ContainerRevokeSubject;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n stillCurrent?: (() => boolean) | undefined;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n}): Promise<{\n containerKey: Uint8Array;\n plan: ContainerRevokePlan;\n response: ContainerRotationResponse;\n} | null> {\n const previousProjection = await input.apiClient.getContainerWriterProjection(\n input.containerId,\n );\n if (!previousProjection) {\n return null;\n }\n\n const materializedPlan = await buildMaterializedContainerRevokePlan({\n author: input.author,\n eventId: input.eventId,\n execSql: input.execSql,\n previousProjection,\n revokedSubject: input.revokedSubject,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n signedAt: input.signedAt,\n stillCurrent: input.stillCurrent,\n targetSecretKey: input.targetSecretKey,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n return submitAcknowledgedContainerMutation({\n reportSecurityIncident: input.reportSecurityIncident,\n recitationPolicies: [],\n apiClient: input.apiClient,\n author: input.author,\n carriedRekeys: {\n planning: {\n apiClient: input.apiClient,\n author: input.author,\n execSql: input.execSql,\n knownContainerKeks: new Map([\n [\n materializedPlan.plan.containerKeyEpochId,\n materializedPlan.containerKey,\n ],\n ]),\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n targetSecretKey: input.targetSecretKey,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n },\n rotated: () =>\n containerWriterProjectionFromRotationPlan({\n plan: materializedPlan.plan,\n previousProjection,\n }),\n },\n containerKey: materializedPlan.containerKey,\n execSql: input.execSql,\n plan: materializedPlan.plan,\n stillCurrent: input.stillCurrent,\n submit: (carried) => {\n const request = carried.length\n ? { ...materializedPlan.plan.request, containerRekeys: [...carried] }\n : materializedPlan.plan.request;\n const options = {\n expectedPaymentRequiredOrganizationId: input.author.organizationId,\n };\n const { revokeContainerResult } = input.apiClient;\n return submitContainerRotation({\n plain: () =>\n input.apiClient.revokeContainer(input.containerId, request, options),\n result: revokeContainerResult\n ? () =>\n revokeContainerResult.call(\n input.apiClient,\n input.containerId,\n request,\n { ...options, reportErrors: false },\n )\n : undefined,\n });\n },\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"revoke.js","sourceRoot":"","sources":["revoke.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccessEvent,\n AccessManifest,\n ContainerAccessManifestState,\n ContainerKekKeyring,\n ContainerKekPredecessorBridge,\n ContainerKeyEpoch,\n ContainerKeyWrap,\n ContainerRevokeAccessEventBody,\n ContainerUserRecipientKey,\n} from \"@tearleads/crypto\";\nimport {\n computeContainerKekKeyringHash,\n computeContainerKekPredecessorBridgeHash,\n deriveContainerKekWrappingPublicKey,\n KeyingVerificationError,\n} from \"@tearleads/crypto\";\nimport type {\n AccessManifestBundleWire,\n ContainerMutationRequest,\n} from \"@tearleads/validators/request\";\nimport type {\n ContainerKekResponse,\n ContainerRotationResponse,\n ContainerWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { assertContainerAuthorAccess } from \"../../../data/containers/shared/authorAccess\";\nimport {\n buildContainerCreateKeyEpoch,\n signContainerMutationEvent,\n} from \"../../../data/containers/shared/events\";\nimport { isStaleParentContainerPathFailure } from \"../../../data/containers/shared/mutationFailures\";\nimport {\n asContainerManifestBundle,\n uniqueSortedManifestHashes,\n} from \"../../../data/containers/shared/projection\";\nimport type {\n ContainerMutationAuthor,\n ContainerRevokeApi,\n ContainerRevokePlan,\n ContainerRotationResult,\n MaterializedContainerRevokePlan,\n} from \"../../../data/containers/shared/types\";\nimport { readCanonicalRecord } from \"../../../data/keying/canonicalJson\";\nimport {\n type PrincipalPolicyCache,\n type ProjectionUserKeyResolver,\n type ReferencedPrincipalPolicyWarmer,\n requireProjectionUserKeyResolver,\n} from \"../../../data/keyingProjectionVerification\";\nimport type { PrincipalPolicyCurrentEvidence } from \"../../../data/principals/principalPolicyEvidence\";\nimport type { SecurityIncidentReporter } from \"../../../data/shared/securityIncidents\";\nimport type { ExecSql } from \"../../../data/sqlite/sqlSchema\";\nimport { buildContainerRotationArtifacts } from \"./move/moveRotation\";\nimport {\n containerMutationRequestCore,\n previousPathRequestFields,\n readCanonicalRecordOrNull,\n} from \"./mutationRequestCore\";\nimport {\n submitAcknowledgedContainerMutation,\n submitContainerRotation,\n} from \"./mutationSubmit\";\nimport { containerWriterProjectionFromRotationPlan } from \"./rekey/rekeyProjection\";\nimport {\n type ContainerRevokeSubject,\n deriveContainerRevokeManifest,\n} from \"./revokeManifest\";\nimport { resolveRotationContext } from \"./rotation/rotationContext\";\nimport { collectContainerRevokePrincipalPolicies } from \"./rotation/rotationPrincipalPolicies\";\nimport { buildContainerRotationWraps } from \"./rotation/rotationWraps\";\n\nfunction buildContainerRevokeRequest(input: {\n body: ContainerRevokeAccessEventBody;\n event: AccessEvent;\n keyEpoch: ContainerKeyEpoch;\n keyring: ContainerKekKeyring;\n manifest: AccessManifest;\n manifestHash: string;\n parentKek: ContainerKekResponse | null;\n predecessorBridge: ContainerKekPredecessorBridge;\n previousManifest: AccessManifestBundleWire;\n previousProjection: ContainerWriterProjectionResponse;\n principalPolicies: readonly PrincipalPolicyCurrentEvidence[];\n userRecipientKeys: readonly ContainerUserRecipientKey[];\n wraps: readonly ContainerKeyWrap[];\n}): ContainerMutationRequest {\n return {\n ...containerMutationRequestCore(\"revoke\", input),\n ...previousPathRequestFields(\n input.previousManifest,\n input.previousProjection,\n ),\n predecessorBridge: readCanonicalRecord(\n input.predecessorBridge,\n \"Container revoke predecessor bridge\",\n ),\n keyring: readCanonicalRecord(input.keyring, \"Container revoke keyring\"),\n parentKekState: readCanonicalRecordOrNull(\n input.parentKek,\n \"Container revoke parent KEK state\",\n ),\n };\n}\n\nfunction buildContainerRevokePlanResult(input: {\n body: ContainerRevokeAccessEventBody;\n containerId: string;\n containerKey: Uint8Array;\n containerKeyEpochId: string;\n event: AccessEvent;\n eventHash: string;\n keyEpoch: ContainerKeyEpoch;\n keyring: ContainerKekKeyring;\n manifest: AccessManifest;\n manifestHash: string;\n parentKek: ContainerKekResponse | null;\n predecessorBridge: ContainerKekPredecessorBridge;\n previousManifest: AccessManifestBundleWire;\n previousProjection: ContainerWriterProjectionResponse;\n principalPolicies: readonly PrincipalPolicyCurrentEvidence[];\n state: ContainerAccessManifestState;\n userRecipientKeys: ContainerUserRecipientKey[];\n wraps: ContainerKeyWrap[];\n}): MaterializedContainerRevokePlan {\n const plan: ContainerRevokePlan = {\n body: input.body,\n containerId: input.containerId,\n containerKeyEpochId: input.containerKeyEpochId,\n event: input.event,\n eventHash: input.eventHash,\n keyEpoch: input.keyEpoch,\n keyring: input.keyring,\n manifest: input.manifest,\n manifestHash: input.manifestHash,\n previousManifest: input.previousManifest,\n request: buildContainerRevokeRequest({\n body: input.body,\n event: input.event,\n keyEpoch: input.keyEpoch,\n keyring: input.keyring,\n manifest: input.manifest,\n manifestHash: input.manifestHash,\n parentKek: input.parentKek,\n predecessorBridge: input.predecessorBridge,\n previousManifest: input.previousManifest,\n previousProjection: input.previousProjection,\n principalPolicies: input.principalPolicies,\n userRecipientKeys: input.userRecipientKeys,\n wraps: input.wraps,\n }),\n state: input.state,\n userRecipientKeys: input.userRecipientKeys,\n wraps: input.wraps,\n };\n\n return { containerKey: input.containerKey, plan };\n}\n\n// biome-ignore lint/complexity/noExcessiveLinesPerFunction: Revoke planning has to keep the manifest, KEK, wraps, and signed event in one auditable transition.\nexport async function buildMaterializedContainerRevokePlan(input: {\n author: ContainerMutationAuthor;\n eventId?: string | undefined;\n execSql: ExecSql;\n /** Keys a batch minted above this container and has yet to commit. */\n knownContainerKeks?: ReadonlyMap<string, Uint8Array> | undefined;\n /** False for a projection re-rooted on rotations not yet acknowledged. */\n persistVerificationCheckpoints?: boolean | undefined;\n previousProjection: ContainerWriterProjectionResponse;\n /** Verified policies the path may cite before they are stored locally. */\n principalPolicyCache?: PrincipalPolicyCache | undefined;\n replacementPrincipalPolicy?: PrincipalPolicyCurrentEvidence | undefined;\n revokedSubject: ContainerRevokeSubject;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n stillCurrent?: (() => boolean) | undefined;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n}): Promise<MaterializedContainerRevokePlan> {\n const resolveProjectionUserKey = requireProjectionUserKeyResolver(\n input.resolveProjectionUserKey,\n \"Remote container revoke\",\n );\n const {\n parentKek,\n parentPublicKey,\n predecessorContainerKey,\n previousState,\n target,\n } = await resolveRotationContext(input, \"revoke\");\n const principalPolicies = await collectContainerRevokePrincipalPolicies({\n execSql: input.execSql,\n persistVerificationCheckpoints: input.persistVerificationCheckpoints,\n previousProjection: input.previousProjection,\n principalPolicyCache: input.principalPolicyCache,\n resolveUserKey: resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n assertContainerAuthorAccess({\n author: input.author,\n projection: input.previousProjection,\n principalPolicies,\n minimumAccess: \"admin\",\n });\n const nextContainerKeyEpoch = target.kek.containerKeyEpoch + 1;\n const { containerKey, containerKeyEpochId, keyring, predecessorBridge } =\n await buildContainerRotationArtifacts({\n containerId: previousState.containerId,\n currentKek: target.kek,\n currentKeyMaterial: predecessorContainerKey,\n keyEpoch: nextContainerKeyEpoch,\n signedEpochIds: target.signedEpochIds,\n });\n const body: ContainerRevokeAccessEventBody = {\n eventType: \"container.revoke\",\n parentManifestHash:\n input.previousProjection.path.at(-2)?.manifestHash ?? null,\n containerKeyPublicKey: await deriveContainerKekWrappingPublicKey({\n containerId: previousState.containerId,\n keyMaterial: containerKey,\n }),\n containerKeyEpochId,\n keyringHash: await computeContainerKekKeyringHash(keyring),\n predecessorBridgeHash:\n await computeContainerKekPredecessorBridgeHash(predecessorBridge),\n subjectId: input.revokedSubject.subjectId,\n subjectType: input.revokedSubject.subjectType,\n };\n const { event, eventHash } = await signContainerMutationEvent({\n author: input.author,\n body,\n containerId: previousState.containerId,\n dependencyManifestHashes: uniqueSortedManifestHashes(\n input.previousProjection.path,\n ),\n eventId: input.eventId ?? crypto.randomUUID(),\n previousManifestHash: target.manifest.manifestHash,\n signedAt: input.signedAt ?? new Date().toISOString(),\n });\n const { manifest, manifestHash, state } = await deriveContainerRevokeManifest(\n {\n containerKeyPublicKey: body.containerKeyPublicKey,\n parentManifestHash: body.parentManifestHash,\n containerKeyEpochId,\n eventHash,\n previousManifest: target.manifest,\n revokedSubject: input.revokedSubject,\n },\n );\n const keyEpoch = buildContainerCreateKeyEpoch({\n containerId: previousState.containerId,\n containerKeyEpochId,\n eventHash,\n keyEpoch: nextContainerKeyEpoch,\n manifestHash,\n parentContainerKeyEpochId: parentKek?.containerKeyEpochId ?? null,\n });\n\n const replacementPrincipalPolicy = input.replacementPrincipalPolicy;\n if (\n replacementPrincipalPolicy &&\n (replacementPrincipalPolicy.principalType !==\n input.revokedSubject.subjectType ||\n replacementPrincipalPolicy.principalId !== input.revokedSubject.subjectId)\n ) {\n throw new Error(\"Container revoke replacement principal does not match\");\n }\n const currentPrincipalPolicies = replacementPrincipalPolicy\n ? principalPolicies.filter(\n (policy) =>\n policy.principalType !== replacementPrincipalPolicy.principalType ||\n policy.principalId !== replacementPrincipalPolicy.principalId,\n )\n : principalPolicies;\n const { userRecipientKeys, wraps } = await buildContainerRotationWraps({\n containerKey,\n containerKeyEpochId,\n manifestHash,\n operationLabel: \"Container revoke\",\n parentKek,\n parentPublicKey,\n principalPolicies: currentPrincipalPolicies,\n resolveUserKey: resolveProjectionUserKey,\n state,\n });\n\n return buildContainerRevokePlanResult({\n body,\n containerId: previousState.containerId,\n containerKey,\n containerKeyEpochId,\n event,\n eventHash,\n keyEpoch,\n keyring,\n manifest,\n manifestHash,\n parentKek,\n predecessorBridge,\n previousManifest: asContainerManifestBundle(target.manifest),\n previousProjection: input.previousProjection,\n principalPolicies: currentPrincipalPolicies,\n state,\n userRecipientKeys,\n wraps,\n });\n}\n\ninterface RevokeRemoteContainerInput {\n reportSecurityIncident: SecurityIncidentReporter;\n apiClient: ContainerRevokeApi;\n author: ContainerMutationAuthor;\n containerId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n revokedSubject: ContainerRevokeSubject;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n stillCurrent?: (() => boolean) | undefined;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n}\n\ntype RevokedRemoteContainer = {\n containerKey: Uint8Array;\n plan: ContainerRevokePlan;\n response: ContainerRotationResponse;\n};\n\nconst STALE_PATH = Symbol(\"stale-revoke-path\");\n\nasync function submitRevocation(\n input: RevokeRemoteContainerInput,\n plan: ContainerRevokePlan,\n carried: readonly ContainerMutationRequest[],\n): Promise<ContainerRotationResult> {\n const request = carried.length\n ? { ...plan.request, containerRekeys: [...carried] }\n : plan.request;\n const options = {\n expectedPaymentRequiredOrganizationId: input.author.organizationId,\n };\n const { revokeContainerResult } = input.apiClient;\n return submitContainerRotation({\n plain: () =>\n input.apiClient.revokeContainer(input.containerId, request, options),\n result: revokeContainerResult\n ? () =>\n revokeContainerResult.call(\n input.apiClient,\n input.containerId,\n request,\n { ...options, reportErrors: false },\n )\n : undefined,\n });\n}\n\nasync function planRevoke(input: RevokeRemoteContainerInput) {\n const previousProjection = await input.apiClient.getContainerWriterProjection(\n input.containerId,\n );\n if (!previousProjection) {\n return null;\n }\n\n const materializedPlan = await buildMaterializedContainerRevokePlan({\n author: input.author,\n eventId: input.eventId,\n execSql: input.execSql,\n previousProjection,\n revokedSubject: input.revokedSubject,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n signedAt: input.signedAt,\n stillCurrent: input.stillCurrent,\n targetSecretKey: input.targetSecretKey,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n return { previousProjection, materializedPlan };\n}\n\nasync function revokeRemoteContainerOnce(\n input: RevokeRemoteContainerInput,\n retriesStalePath: boolean,\n onAccepted: () => void,\n): Promise<RevokedRemoteContainer | typeof STALE_PATH | null> {\n const planned = await planRevoke(input);\n if (!planned) return null;\n const { previousProjection, materializedPlan } = planned;\n let stalePath = false;\n const revoked = await submitAcknowledgedContainerMutation({\n onAccepted,\n reportSecurityIncident: input.reportSecurityIncident,\n recitationPolicies: [],\n apiClient: input.apiClient,\n author: input.author,\n carriedRekeys: {\n planning: {\n apiClient: input.apiClient,\n author: input.author,\n execSql: input.execSql,\n knownContainerKeks: new Map([\n [\n materializedPlan.plan.containerKeyEpochId,\n materializedPlan.containerKey,\n ],\n ]),\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n targetSecretKey: input.targetSecretKey,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n },\n rotated: () =>\n containerWriterProjectionFromRotationPlan({\n plan: materializedPlan.plan,\n previousProjection,\n }),\n },\n containerKey: materializedPlan.containerKey,\n execSql: input.execSql,\n plan: materializedPlan.plan,\n stillCurrent: input.stillCurrent,\n submit: async (carried) => {\n const result = await submitRevocation(\n input,\n materializedPlan.plan,\n carried,\n );\n if (\n !result.ok &&\n retriesStalePath &&\n isStaleParentContainerPathFailure(result)\n ) {\n stalePath = true;\n // The refused transaction rolled back. Replan it without reporting an\n // error that the caller can repair; a second refusal is terminal.\n return { ok: false, status: result.status, code: result.code };\n }\n return result;\n },\n });\n return revoked ?? (stalePath ? STALE_PATH : null);\n}\n\nasync function revokeRemoteContainerAttempt(\n input: RevokeRemoteContainerInput,\n retriesStalePath: boolean,\n): Promise<RevokedRemoteContainer | typeof STALE_PATH | null> {\n let accepted = false;\n try {\n return await revokeRemoteContainerOnce(input, retriesStalePath, () => {\n accepted = true;\n });\n } catch (error) {\n // Our own or a carried descendant's cached projection may trail a pin.\n // Refresh only a refused/unsubmitted attempt, never an accepted rotation.\n if (\n retriesStalePath &&\n !accepted &&\n error instanceof KeyingVerificationError &&\n error.code === \"rollback\"\n )\n return STALE_PATH;\n throw error;\n }\n}\n\n/** A concurrent mutation can advance the path while a revoke is in flight. */\nexport async function revokeRemoteContainer(\n input: RevokeRemoteContainerInput,\n): Promise<RevokedRemoteContainer | null> {\n if (input.stillCurrent?.() === false) return null;\n const first = await revokeRemoteContainerAttempt(input, true);\n if (first !== STALE_PATH) return first;\n if (input.stillCurrent?.() === false) return null;\n input.apiClient.evictContainerWriterProjection?.(input.containerId);\n input.apiClient.evictWriterProjectionsCiting?.([input.containerId]);\n const second = await revokeRemoteContainerAttempt(input, false);\n return second === STALE_PATH ? null : second;\n}\n"]}
|