@tearleads/client-sdk 0.1.99 → 0.1.101
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/containers/shared/mutationFailures.js.map +1 -1
- package/data/containers/shared/types.d.ts +3 -0
- package/data/containers/shared/types.js.map +1 -1
- package/internal/api-client/ApiClient.d.ts +2 -0
- package/internal/api-client/ApiClient.js +13 -11
- package/internal/api-client/ApiClient.js.map +1 -1
- package/package.json +1 -1
- package/workflows/containers/child/share/index.d.ts +14 -4
- package/workflows/containers/child/share/index.js +93 -30
- package/workflows/containers/child/share/index.js.map +1 -1
- package/workflows/containers/rematerialization/principalContainerRematerialization.js +8 -24
- package/workflows/containers/rematerialization/principalContainerRematerialization.js.map +1 -1
- package/workflows/containers/rematerialization/principalContainerRematerializationPlanning.js +72 -1
- package/workflows/containers/rematerialization/principalContainerRematerializationPlanning.js.map +1 -1
- package/workflows/organizations/groupPolicy/commitCurrentGroupPolicyMutation.js +22 -16
- package/workflows/organizations/groupPolicy/commitCurrentGroupPolicyMutation.js.map +1 -1
- package/workflows/organizations/groupShare/currentShareGrantMutation.js +4 -3
- package/workflows/organizations/groupShare/currentShareGrantMutation.js.map +1 -1
- package/workflows/organizations/groupShare/sharePrincipalPolicy.d.ts +1 -0
- package/workflows/organizations/groupShare/sharePrincipalPolicy.js.map +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { KeyingVerificationError, } from "../../../../internal/crypto/index.js";
|
|
2
|
+
import { isStaleParentContainerPathFailure } from "../../../../data/containers/shared/mutationFailures.js";
|
|
1
3
|
import { nullOnProjectionVerificationCancellation, requireProjectionUserKeyResolver, } from "../../../../data/keyingProjectionVerification/index.js";
|
|
2
4
|
import { requireTrustedUserIdentityResolver, } from "../../../../data/trustedUserIdentity/index.js";
|
|
3
5
|
import { submitAcknowledgedContainerMutation, submitPlainContainerMutation, } from "../mutationSubmit.js";
|
|
@@ -6,36 +8,68 @@ import { resolveShareProjection } from "./shareProjection.js";
|
|
|
6
8
|
async function buildCurrentContainerSharePlan(input) {
|
|
7
9
|
return nullOnProjectionVerificationCancellation(() => buildMaterializedContainerSharePlan(input));
|
|
8
10
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
/** A path a head advanced under, which a share plans once more (#2647). */
|
|
12
|
+
const STALE_PATH = Symbol("stale-path");
|
|
13
|
+
/**
|
|
14
|
+
* Submit through the status-bearing endpoint when the adapter has one. The
|
|
15
|
+
* refusal that ends the share is reported once submission settles, as the
|
|
16
|
+
* plain endpoint's would be; a stale path the share plans again is not.
|
|
17
|
+
*/
|
|
18
|
+
async function submitShare(input, request, retriesStalePath) {
|
|
19
|
+
const options = {
|
|
20
|
+
expectedPaymentRequiredOrganizationId: input.author.organizationId,
|
|
21
|
+
};
|
|
22
|
+
const { shareContainerResult } = input.apiClient;
|
|
23
|
+
if (!shareContainerResult) {
|
|
24
|
+
return submitPlainContainerMutation(() => input.apiClient.shareContainer(input.containerId, request, options));
|
|
25
|
+
}
|
|
26
|
+
const result = await shareContainerResult.call(input.apiClient, input.containerId, request, { ...options, reportErrors: false });
|
|
27
|
+
if (result.ok)
|
|
28
|
+
return { ok: true, data: result.data };
|
|
29
|
+
return retriesStalePath && isStaleParentContainerPathFailure(result)
|
|
30
|
+
? { ok: false, code: result.code, status: result.status }
|
|
31
|
+
: result;
|
|
32
|
+
}
|
|
33
|
+
function planShare(input, recipientEncapsulationPublicKey, resolveProjectionUserKey) {
|
|
34
|
+
return resolveShareProjection(input, resolveProjectionUserKey).then((previousProjection) => previousProjection &&
|
|
35
|
+
buildCurrentContainerSharePlan({
|
|
36
|
+
accessLevel: input.accessLevel,
|
|
37
|
+
author: input.author,
|
|
38
|
+
eventId: input.eventId,
|
|
39
|
+
execSql: input.execSql,
|
|
40
|
+
previousProjection,
|
|
41
|
+
recipient: {
|
|
42
|
+
recipientEncapsulationPublicKey,
|
|
43
|
+
subjectId: input.recipientUserId,
|
|
44
|
+
subjectType: "user",
|
|
45
|
+
},
|
|
46
|
+
resolveProjectionUserKey,
|
|
47
|
+
signedAt: input.signedAt,
|
|
48
|
+
stillCurrent: input.stillCurrent,
|
|
49
|
+
targetSecretKey: input.targetSecretKey,
|
|
50
|
+
warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
async function shareRemoteContainerOnce(input, recipientEncapsulationPublicKey, resolveProjectionUserKey, retriesStalePath) {
|
|
54
|
+
let materializedPlan;
|
|
55
|
+
try {
|
|
56
|
+
materializedPlan = await planShare(input, recipientEncapsulationPublicKey, resolveProjectionUserKey);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// The path the share was handed predates a pin this device has since
|
|
60
|
+
// advanced, most often through its own re-citation. A fresh projection
|
|
61
|
+
// is held to the same pins, so a server that rolls a head back still
|
|
62
|
+
// fails the share, on its second plan.
|
|
63
|
+
if (retriesStalePath &&
|
|
64
|
+
error instanceof KeyingVerificationError &&
|
|
65
|
+
error.code === "rollback")
|
|
66
|
+
return STALE_PATH;
|
|
67
|
+
throw error;
|
|
15
68
|
}
|
|
16
|
-
const previousProjection = await resolveShareProjection(input, resolveProjectionUserKey);
|
|
17
|
-
if (!previousProjection)
|
|
18
|
-
return null;
|
|
19
|
-
const materializedPlan = await buildCurrentContainerSharePlan({
|
|
20
|
-
accessLevel: input.accessLevel,
|
|
21
|
-
author: input.author,
|
|
22
|
-
eventId: input.eventId,
|
|
23
|
-
execSql: input.execSql,
|
|
24
|
-
previousProjection,
|
|
25
|
-
recipient: {
|
|
26
|
-
recipientEncapsulationPublicKey: recipientIdentity.encapsulationPublicKey,
|
|
27
|
-
subjectId: input.recipientUserId,
|
|
28
|
-
subjectType: "user",
|
|
29
|
-
},
|
|
30
|
-
resolveProjectionUserKey,
|
|
31
|
-
signedAt: input.signedAt,
|
|
32
|
-
stillCurrent: input.stillCurrent,
|
|
33
|
-
targetSecretKey: input.targetSecretKey,
|
|
34
|
-
warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,
|
|
35
|
-
});
|
|
36
69
|
if (!materializedPlan)
|
|
37
70
|
return null;
|
|
38
|
-
|
|
71
|
+
let stalePath = false;
|
|
72
|
+
const shared = await submitAcknowledgedContainerMutation({
|
|
39
73
|
reportSecurityIncident: input.reportSecurityIncident,
|
|
40
74
|
recitationPolicies: [],
|
|
41
75
|
apiClient: input.apiClient,
|
|
@@ -44,9 +78,38 @@ export async function shareRemoteContainer(input) {
|
|
|
44
78
|
execSql: input.execSql,
|
|
45
79
|
plan: materializedPlan.plan,
|
|
46
80
|
stillCurrent: input.stillCurrent,
|
|
47
|
-
submit:
|
|
48
|
-
|
|
49
|
-
|
|
81
|
+
submit: async () => {
|
|
82
|
+
const result = await submitShare(input, materializedPlan.plan.request, retriesStalePath);
|
|
83
|
+
stalePath =
|
|
84
|
+
!result.ok &&
|
|
85
|
+
retriesStalePath &&
|
|
86
|
+
isStaleParentContainerPathFailure(result);
|
|
87
|
+
return result;
|
|
88
|
+
},
|
|
50
89
|
});
|
|
90
|
+
return shared ?? (stalePath ? STALE_PATH : null);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Share a container with a user. A head on the signed path can advance while
|
|
94
|
+
* the share is in flight, most often under this device's own background
|
|
95
|
+
* re-citation of held descendants after a share above it. The API refuses
|
|
96
|
+
* that as a stale path, and the share is planned once more from a fresh
|
|
97
|
+
* projection, as a create under a stale parent is.
|
|
98
|
+
*/
|
|
99
|
+
export async function shareRemoteContainer(input) {
|
|
100
|
+
const resolveProjectionUserKey = requireProjectionUserKeyResolver(input.resolveProjectionUserKey, "Remote container share");
|
|
101
|
+
const resolveTrustedUserIdentity = requireTrustedUserIdentityResolver(input.resolveTrustedUserIdentity);
|
|
102
|
+
const recipientIdentity = await resolveTrustedUserIdentity(input.recipientUserId);
|
|
103
|
+
if (!recipientIdentity || input.stillCurrent?.() === false) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const first = await shareRemoteContainerOnce(input, recipientIdentity.encapsulationPublicKey, resolveProjectionUserKey, true);
|
|
107
|
+
if (first !== STALE_PATH)
|
|
108
|
+
return first;
|
|
109
|
+
if (input.stillCurrent?.() === false)
|
|
110
|
+
return null;
|
|
111
|
+
input.apiClient.evictContainerWriterProjection?.(input.containerId);
|
|
112
|
+
const second = await shareRemoteContainerOnce({ ...input, previousProjection: undefined }, recipientIdentity.encapsulationPublicKey, resolveProjectionUserKey, false);
|
|
113
|
+
return second === STALE_PATH ? null : second;
|
|
51
114
|
}
|
|
52
115
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,wCAAwC,EAGxC,gCAAgC,GACjC,MAAM,+CAA+C,CAAC;AAGvD,OAAO,EACL,kCAAkC,GAEnC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,mCAAmC,EACnC,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,mCAAmC,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,KAAK,UAAU,8BAA8B,CAC3C,KAAgE;IAEhE,OAAO,wCAAwC,CAAC,GAAG,EAAE,CACnD,mCAAmC,CAAC,KAAK,CAAC,CAC3C,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAgB1C;IAKC,MAAM,wBAAwB,GAAG,gCAAgC,CAC/D,KAAK,CAAC,wBAAwB,EAC9B,wBAAwB,CACzB,CAAC;IACF,MAAM,0BAA0B,GAAG,kCAAkC,CACnE,KAAK,CAAC,0BAA0B,CACjC,CAAC;IACF,MAAM,iBAAiB,GAAG,MAAM,0BAA0B,CACxD,KAAK,CAAC,eAAe,CACtB,CAAC;IACF,IAAI,CAAC,iBAAiB,IAAI,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK,EAAE,CAAC;QAC3D,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,kBAAkB,GAAG,MAAM,sBAAsB,CACrD,KAAK,EACL,wBAAwB,CACzB,CAAC;IACF,IAAI,CAAC,kBAAkB;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,gBAAgB,GAAG,MAAM,8BAA8B,CAAC;QAC5D,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,kBAAkB;QAClB,SAAS,EAAE;YACT,+BAA+B,EAAE,iBAAiB,CAAC,sBAAsB;YACzE,SAAS,EAAE,KAAK,CAAC,eAAe;YAChC,WAAW,EAAE,MAAM;SACpB;QACD,wBAAwB;QACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,+BAA+B,EAAE,KAAK,CAAC,+BAA+B;KACvE,CAAC,CAAC;IACH,IAAI,CAAC,gBAAgB;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,mCAAmC,CAAC;QACzC,sBAAsB,EAAE,KAAK,CAAC,sBAAsB;QACpD,kBAAkB,EAAE,EAAE;QACtB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,YAAY,EAAE,gBAAgB,CAAC,YAAY;QAC3C,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,EAAE,gBAAgB,CAAC,IAAI;QAC3B,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,MAAM,EAAE,GAAG,EAAE,CACX,4BAA4B,CAAC,GAAG,EAAE,CAChC,KAAK,CAAC,SAAS,CAAC,cAAc,CAC5B,KAAK,CAAC,WAAW,EACjB,gBAAgB,CAAC,IAAI,CAAC,OAAO,EAC7B;YACE,qCAAqC,EAAE,KAAK,CAAC,MAAM,CAAC,cAAc;SACnE,CACF,CACF;KACJ,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { ContainerAccessLevel } from \"@tearleads/crypto\";\nimport type {\n ContainerMutationResponse,\n ContainerWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport type {\n ContainerMutationAuthor,\n ContainerShareApi,\n ContainerSharePlan,\n MaterializedContainerSharePlan,\n} from \"../../../../data/containers/shared/types\";\nimport {\n nullOnProjectionVerificationCancellation,\n type ProjectionUserKeyResolver,\n type ReferencedPrincipalPolicyWarmer,\n requireProjectionUserKeyResolver,\n} from \"../../../../data/keyingProjectionVerification\";\nimport type { SecurityIncidentReporter } from \"../../../../data/shared/securityIncidents\";\nimport type { ExecSql } from \"../../../../data/sqlite/sqlSchema\";\nimport {\n requireTrustedUserIdentityResolver,\n type TrustedUserIdentityResolver,\n} from \"../../../../data/trustedUserIdentity\";\nimport {\n submitAcknowledgedContainerMutation,\n submitPlainContainerMutation,\n} from \"../mutationSubmit\";\nimport { buildMaterializedContainerSharePlan } from \"./shareMaterialization\";\nimport { resolveShareProjection } from \"./shareProjection\";\n\nasync function buildCurrentContainerSharePlan(\n input: Parameters<typeof buildMaterializedContainerSharePlan>[0],\n): Promise<MaterializedContainerSharePlan | null> {\n return nullOnProjectionVerificationCancellation(() =>\n buildMaterializedContainerSharePlan(input),\n );\n}\n\nexport async function shareRemoteContainer(input: {\n reportSecurityIncident: SecurityIncidentReporter;\n accessLevel: ContainerAccessLevel;\n apiClient: ContainerShareApi;\n author: ContainerMutationAuthor;\n containerId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n previousProjection?: ContainerWriterProjectionResponse | undefined;\n recipientUserId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n signedAt?: string | undefined;\n stillCurrent?: (() => boolean) | undefined;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n}): Promise<{\n containerKey: Uint8Array;\n plan: ContainerSharePlan;\n response: ContainerMutationResponse;\n} | null> {\n const resolveProjectionUserKey = requireProjectionUserKeyResolver(\n input.resolveProjectionUserKey,\n \"Remote container share\",\n );\n const resolveTrustedUserIdentity = requireTrustedUserIdentityResolver(\n input.resolveTrustedUserIdentity,\n );\n const recipientIdentity = await resolveTrustedUserIdentity(\n input.recipientUserId,\n );\n if (!recipientIdentity || input.stillCurrent?.() === false) {\n return null;\n }\n const previousProjection = await resolveShareProjection(\n input,\n resolveProjectionUserKey,\n );\n if (!previousProjection) return null;\n\n const materializedPlan = await buildCurrentContainerSharePlan({\n accessLevel: input.accessLevel,\n author: input.author,\n eventId: input.eventId,\n execSql: input.execSql,\n previousProjection,\n recipient: {\n recipientEncapsulationPublicKey: recipientIdentity.encapsulationPublicKey,\n subjectId: input.recipientUserId,\n subjectType: \"user\",\n },\n resolveProjectionUserKey,\n signedAt: input.signedAt,\n stillCurrent: input.stillCurrent,\n targetSecretKey: input.targetSecretKey,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n if (!materializedPlan) return null;\n return submitAcknowledgedContainerMutation({\n reportSecurityIncident: input.reportSecurityIncident,\n recitationPolicies: [],\n apiClient: input.apiClient,\n author: input.author,\n containerKey: materializedPlan.containerKey,\n execSql: input.execSql,\n plan: materializedPlan.plan,\n stillCurrent: input.stillCurrent,\n submit: () =>\n submitPlainContainerMutation(() =>\n input.apiClient.shareContainer(\n input.containerId,\n materializedPlan.plan.request,\n {\n expectedPaymentRequiredOrganizationId: input.author.organizationId,\n },\n ),\n ),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type ContainerAccessLevel,\n KeyingVerificationError,\n} from \"@tearleads/crypto\";\nimport type { ContainerMutationRequest } from \"@tearleads/validators/request\";\nimport type {\n ContainerMutationResponse,\n ContainerWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { isStaleParentContainerPathFailure } from \"../../../../data/containers/shared/mutationFailures\";\nimport type {\n ContainerMutationAuthor,\n ContainerRotationResult,\n ContainerShareApi,\n ContainerSharePlan,\n MaterializedContainerSharePlan,\n} from \"../../../../data/containers/shared/types\";\nimport {\n nullOnProjectionVerificationCancellation,\n type ProjectionUserKeyResolver,\n type ReferencedPrincipalPolicyWarmer,\n requireProjectionUserKeyResolver,\n} from \"../../../../data/keyingProjectionVerification\";\nimport type { SecurityIncidentReporter } from \"../../../../data/shared/securityIncidents\";\nimport type { ExecSql } from \"../../../../data/sqlite/sqlSchema\";\nimport {\n requireTrustedUserIdentityResolver,\n type TrustedUserIdentityResolver,\n} from \"../../../../data/trustedUserIdentity\";\nimport {\n submitAcknowledgedContainerMutation,\n submitPlainContainerMutation,\n} from \"../mutationSubmit\";\nimport { buildMaterializedContainerSharePlan } from \"./shareMaterialization\";\nimport { resolveShareProjection } from \"./shareProjection\";\n\nasync function buildCurrentContainerSharePlan(\n input: Parameters<typeof buildMaterializedContainerSharePlan>[0],\n): Promise<MaterializedContainerSharePlan | null> {\n return nullOnProjectionVerificationCancellation(() =>\n buildMaterializedContainerSharePlan(input),\n );\n}\n\ntype ShareRemoteContainerInput = {\n reportSecurityIncident: SecurityIncidentReporter;\n accessLevel: ContainerAccessLevel;\n apiClient: ContainerShareApi;\n author: ContainerMutationAuthor;\n containerId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n previousProjection?: ContainerWriterProjectionResponse | undefined;\n recipientUserId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n signedAt?: string | undefined;\n stillCurrent?: (() => boolean) | undefined;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n};\n\ntype SharedRemoteContainer = {\n containerKey: Uint8Array;\n plan: ContainerSharePlan;\n response: ContainerMutationResponse;\n};\n\n/** A path a head advanced under, which a share plans once more (#2647). */\nconst STALE_PATH = Symbol(\"stale-path\");\n\n/**\n * Submit through the status-bearing endpoint when the adapter has one. The\n * refusal that ends the share is reported once submission settles, as the\n * plain endpoint's would be; a stale path the share plans again is not.\n */\nasync function submitShare(\n input: ShareRemoteContainerInput,\n request: ContainerMutationRequest,\n retriesStalePath: boolean,\n): Promise<ContainerRotationResult> {\n const options = {\n expectedPaymentRequiredOrganizationId: input.author.organizationId,\n };\n const { shareContainerResult } = input.apiClient;\n if (!shareContainerResult) {\n return submitPlainContainerMutation(() =>\n input.apiClient.shareContainer(input.containerId, request, options),\n );\n }\n const result = await shareContainerResult.call(\n input.apiClient,\n input.containerId,\n request,\n { ...options, reportErrors: false },\n );\n if (result.ok) return { ok: true, data: result.data };\n return retriesStalePath && isStaleParentContainerPathFailure(result)\n ? { ok: false, code: result.code, status: result.status }\n : result;\n}\n\nfunction planShare(\n input: ShareRemoteContainerInput,\n recipientEncapsulationPublicKey: Uint8Array,\n resolveProjectionUserKey: ProjectionUserKeyResolver,\n) {\n return resolveShareProjection(input, resolveProjectionUserKey).then(\n (previousProjection) =>\n previousProjection &&\n buildCurrentContainerSharePlan({\n accessLevel: input.accessLevel,\n author: input.author,\n eventId: input.eventId,\n execSql: input.execSql,\n previousProjection,\n recipient: {\n recipientEncapsulationPublicKey,\n subjectId: input.recipientUserId,\n subjectType: \"user\",\n },\n resolveProjectionUserKey,\n signedAt: input.signedAt,\n stillCurrent: input.stillCurrent,\n targetSecretKey: input.targetSecretKey,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n }),\n );\n}\n\nasync function shareRemoteContainerOnce(\n input: ShareRemoteContainerInput,\n recipientEncapsulationPublicKey: Uint8Array,\n resolveProjectionUserKey: ProjectionUserKeyResolver,\n retriesStalePath: boolean,\n): Promise<SharedRemoteContainer | typeof STALE_PATH | null> {\n let materializedPlan: Awaited<ReturnType<typeof planShare>>;\n try {\n materializedPlan = await planShare(\n input,\n recipientEncapsulationPublicKey,\n resolveProjectionUserKey,\n );\n } catch (error) {\n // The path the share was handed predates a pin this device has since\n // advanced, most often through its own re-citation. A fresh projection\n // is held to the same pins, so a server that rolls a head back still\n // fails the share, on its second plan.\n if (\n retriesStalePath &&\n error instanceof KeyingVerificationError &&\n error.code === \"rollback\"\n )\n return STALE_PATH;\n throw error;\n }\n if (!materializedPlan) return null;\n let stalePath = false;\n const shared = await submitAcknowledgedContainerMutation({\n reportSecurityIncident: input.reportSecurityIncident,\n recitationPolicies: [],\n apiClient: input.apiClient,\n author: input.author,\n containerKey: materializedPlan.containerKey,\n execSql: input.execSql,\n plan: materializedPlan.plan,\n stillCurrent: input.stillCurrent,\n submit: async () => {\n const result = await submitShare(\n input,\n materializedPlan.plan.request,\n retriesStalePath,\n );\n stalePath =\n !result.ok &&\n retriesStalePath &&\n isStaleParentContainerPathFailure(result);\n return result;\n },\n });\n return shared ?? (stalePath ? STALE_PATH : null);\n}\n\n/**\n * Share a container with a user. A head on the signed path can advance while\n * the share is in flight, most often under this device's own background\n * re-citation of held descendants after a share above it. The API refuses\n * that as a stale path, and the share is planned once more from a fresh\n * projection, as a create under a stale parent is.\n */\nexport async function shareRemoteContainer(\n input: ShareRemoteContainerInput,\n): Promise<SharedRemoteContainer | null> {\n const resolveProjectionUserKey = requireProjectionUserKeyResolver(\n input.resolveProjectionUserKey,\n \"Remote container share\",\n );\n const resolveTrustedUserIdentity = requireTrustedUserIdentityResolver(\n input.resolveTrustedUserIdentity,\n );\n const recipientIdentity = await resolveTrustedUserIdentity(\n input.recipientUserId,\n );\n if (!recipientIdentity || input.stillCurrent?.() === false) {\n return null;\n }\n const first = await shareRemoteContainerOnce(\n input,\n recipientIdentity.encapsulationPublicKey,\n resolveProjectionUserKey,\n true,\n );\n if (first !== STALE_PATH) return first;\n if (input.stillCurrent?.() === false) return null;\n input.apiClient.evictContainerWriterProjection?.(input.containerId);\n const second = await shareRemoteContainerOnce(\n { ...input, previousProjection: undefined },\n recipientIdentity.encapsulationPublicKey,\n resolveProjectionUserKey,\n false,\n );\n return second === STALE_PATH ? null : second;\n}\n"]}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { MAX_ROTATION_CONTAINER_REKEYS } from "../../../internal/validators/util/index.js";
|
|
2
1
|
import { rememberVerifiedContainerHeads } from "../../../data/containers/shared/heldContainerHeads.js";
|
|
3
2
|
import { acknowledgeContainerMutationBatch } from "../../../data/containers/shared/mutationAcknowledgement.js";
|
|
4
3
|
import { getTargetContainerContext, readContainerState, } from "../../../data/containers/shared/projection.js";
|
|
@@ -12,7 +11,7 @@ import { buildMaterializedContainerRevokePlan } from "../child/revoke.js";
|
|
|
12
11
|
import { selectReplacementPrincipalPolicyReferences } from "../child/rotation/replacementPrincipalPolicyReferences.js";
|
|
13
12
|
import { buildMaterializedContainerSharePlan } from "../child/share/shareMaterialization.js";
|
|
14
13
|
import { withPrecedingRematerialization } from "./precedingPrincipalRematerialization.js";
|
|
15
|
-
import { addPlan, carryLevel, staleLevelsAbove, } from "./principalContainerRematerializationPlanning.js";
|
|
14
|
+
import { addPlan, assertNamedContainersRotated, carryLevel, planOnceMoreAfterRollback, rerootOnAuthoredPaths, staleLevelsAbove, } from "./principalContainerRematerializationPlanning.js";
|
|
16
15
|
import { loadRematerializationTargets, loadServedProjection, seededPrincipalPolicyCache, } from "./principalContainerRematerializationTargets.js";
|
|
17
16
|
function matchingGroupGrant(input) {
|
|
18
17
|
return (input.directGrants.find((grant) => grant.subjectType === "group" && grant.subjectId === input.groupId) ?? null);
|
|
@@ -143,7 +142,7 @@ async function buildPrincipalContainerRematerializationPlan(input) {
|
|
|
143
142
|
*/
|
|
144
143
|
async function planRematerializationTarget(batch, target) {
|
|
145
144
|
const { rematerialization } = batch;
|
|
146
|
-
|
|
145
|
+
const rebased = rebaseOnDeepestAncestor(target.projection, batch.rotatedAbove);
|
|
147
146
|
const stale = rebased
|
|
148
147
|
? staleLevelsAbove(rebased.projection, rebased.ancestor.containerKeks.at(-1)?.containerId ?? "")
|
|
149
148
|
: [];
|
|
@@ -155,10 +154,7 @@ async function planRematerializationTarget(batch, target) {
|
|
|
155
154
|
return;
|
|
156
155
|
}
|
|
157
156
|
// Each level carried above re-roots the target one step deeper.
|
|
158
|
-
|
|
159
|
-
rebased = rebaseOnDeepestAncestor(target.projection, batch.rotatedAbove);
|
|
160
|
-
}
|
|
161
|
-
const previousProjection = rebased?.projection ?? target.projection;
|
|
157
|
+
const previousProjection = await rerootOnAuthoredPaths(batch, target.projection);
|
|
162
158
|
const planned = await buildPrincipalContainerRematerializationPlan({
|
|
163
159
|
grantRow: target.grantRow,
|
|
164
160
|
knownContainerKeks: batch.knownContainerKeks,
|
|
@@ -189,7 +185,10 @@ async function planRematerializationTarget(batch, target) {
|
|
|
189
185
|
* rotation it rides, and a named container the batch rematerializes anyway is
|
|
190
186
|
* re-planned there rather than rotated twice.
|
|
191
187
|
*/
|
|
192
|
-
|
|
188
|
+
function buildPrincipalContainerRematerializationPlans(input, carriedContainerIds = []) {
|
|
189
|
+
return planOnceMoreAfterRollback(input.apiClient, [...input.grants.map((grant) => grant.containerId), ...carriedContainerIds], () => planRematerializationBatch(input, carriedContainerIds));
|
|
190
|
+
}
|
|
191
|
+
async function planRematerializationBatch(input, carriedContainerIds) {
|
|
193
192
|
input = withPrecedingRematerialization(input);
|
|
194
193
|
if (input.revokedContainerId &&
|
|
195
194
|
!input.grants.some((grant) => grant.containerId === input.revokedContainerId)) {
|
|
@@ -216,6 +215,7 @@ async function buildPrincipalContainerRematerializationPlans(input, carriedConta
|
|
|
216
215
|
resolveTrustedUserIdentity: input.resolveTrustedUserIdentity,
|
|
217
216
|
}),
|
|
218
217
|
rotatedAbove: [],
|
|
218
|
+
authoredAbove: [],
|
|
219
219
|
};
|
|
220
220
|
for (const target of targets) {
|
|
221
221
|
await planRematerializationTarget(batch, target);
|
|
@@ -233,22 +233,6 @@ async function buildPrincipalContainerRematerializationPlans(input, carriedConta
|
|
|
233
233
|
},
|
|
234
234
|
};
|
|
235
235
|
}
|
|
236
|
-
/**
|
|
237
|
-
* The server names a level it found pinned to a retired epoch. A batch answers
|
|
238
|
-
* with a rotation of it: a carried rekey, or its own rematerialization when
|
|
239
|
-
* that is a rekey or revoke. A rematerialized grant rotates nothing, so a
|
|
240
|
-
* batch that both grants a container and must re-key it cannot be signed;
|
|
241
|
-
* no policy change the SDK builds combines the two.
|
|
242
|
-
*/
|
|
243
|
-
function assertNamedContainersRotated(plans, requiredContainerIds) {
|
|
244
|
-
const rotatedIds = new Set(plans.flatMap((entry) => entry.rotated ? [entry.planned.plan.containerId] : []));
|
|
245
|
-
const unrotated = requiredContainerIds
|
|
246
|
-
.slice(0, MAX_ROTATION_CONTAINER_REKEYS)
|
|
247
|
-
.find((containerId) => !rotatedIds.has(containerId));
|
|
248
|
-
if (unrotated !== undefined) {
|
|
249
|
-
throw new Error(`Container ${unrotated} needs a rekey this policy change only grants`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
236
|
export async function preparePrincipalContainerRematerializationBatch(input) {
|
|
253
237
|
let { entries, retiredContainerIds, preceding } = await buildPrincipalContainerRematerializationPlans(input);
|
|
254
238
|
const requests = () => entries.map((entry) => entry.planned.plan.request);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"principalContainerRematerialization.js","sourceRoot":"","sources":["principalContainerRematerialization.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ContainerDirectGrant,\n PrincipalContainerGrant,\n} from \"@tearleads/crypto\";\nimport type { ContainerMutationRequest } from \"@tearleads/validators/request\";\nimport type {\n ContainerMutationResponse,\n ContainerWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { MAX_ROTATION_CONTAINER_REKEYS } from \"@tearleads/validators/util\";\nimport { rememberVerifiedContainerHeads } from \"../../../data/containers/shared/heldContainerHeads\";\nimport type { AuthoredContainerMutationHead } from \"../../../data/containers/shared/mutationAcknowledgement\";\nimport { acknowledgeContainerMutationBatch } from \"../../../data/containers/shared/mutationAcknowledgement\";\nimport {\n getTargetContainerContext,\n readContainerState,\n} from \"../../../data/containers/shared/projection\";\nimport {\n type PrincipalPolicyCache,\n verifyContainerWriterProjection,\n} from \"../../../data/keyingProjectionVerification\";\nimport { createProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification/userKeyResolver\";\nimport { scheduleHeldDescendantRecitations } from \"../child/recite\";\nimport { buildMaterializedContainerRekeyPlan } from \"../child/rekey\";\nimport { rebaseOnDeepestAncestor } from \"../child/rekey/carriedDescendantRekeys\";\nimport {\n containerWriterProjectionFromRotationPlan,\n containerWriterProjectionFromSharePlan,\n isSpeculativeContainerWriterProjection,\n} from \"../child/rekey/rekeyProjection\";\nimport { buildMaterializedContainerRevokePlan } from \"../child/revoke\";\nimport { selectReplacementPrincipalPolicyReferences } from \"../child/rotation/replacementPrincipalPolicyReferences\";\nimport { buildMaterializedContainerSharePlan } from \"../child/share/shareMaterialization\";\nimport { withPrecedingRematerialization } from \"./precedingPrincipalRematerialization\";\nimport {\n addPlan,\n type BatchPlanning,\n carryLevel,\n type PrincipalContainerRematerializationInput,\n type PrincipalRematerializationPredecessor,\n staleLevelsAbove,\n} from \"./principalContainerRematerializationPlanning\";\nimport {\n loadRematerializationTargets,\n loadServedProjection,\n type MaterializedPrincipalContainerMutationPlan,\n type PlannedRematerialization,\n type RematerializationTarget,\n seededPrincipalPolicyCache,\n} from \"./principalContainerRematerializationTargets\";\n\nfunction matchingGroupGrant(input: {\n readonly directGrants: readonly ContainerDirectGrant[];\n readonly groupId: string;\n}): ContainerDirectGrant | null {\n return (\n input.directGrants.find(\n (grant) =>\n grant.subjectType === \"group\" && grant.subjectId === input.groupId,\n ) ?? null\n );\n}\n\nfunction referencedGroupKeyEpoch(input: {\n readonly groupId: string;\n readonly referencedPrincipalHeads: ReturnType<\n typeof readContainerState\n >[\"referencedPrincipalHeads\"];\n}): number | null {\n return (\n input.referencedPrincipalHeads.find(\n (head) =>\n head.principalType === \"group\" && head.principalId === input.groupId,\n )?.keyEpoch ?? null\n );\n}\n\nasync function loadGrantedContainerContext(\n input: PrincipalContainerRematerializationInput,\n grantRow: PrincipalContainerGrant,\n projection: ContainerWriterProjectionResponse,\n principalPolicyCache: PrincipalPolicyCache,\n) {\n // A projection re-rooted on a rotation this batch has yet to commit must\n // not pin anything: its heads are speculative until acknowledged, and a\n // pin past the group's served head would refuse the other served paths.\n await verifyContainerWriterProjection({\n execSql: input.execSql,\n persistVerificationCheckpoints:\n !isSpeculativeContainerWriterProjection(projection),\n principalPolicyCache,\n projection,\n resolveUserKey: createProjectionUserKeyResolver({\n resolveTrustedUserIdentity: input.resolveTrustedUserIdentity,\n }),\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n const state = readContainerState(\n getTargetContainerContext(projection).manifest,\n );\n const grant = matchingGroupGrant({\n directGrants: state.directGrants,\n groupId: input.groupId,\n });\n if (grant && grant.accessLevel !== grantRow.accessLevel) {\n const nextGrant = input.nextPolicy.grants.find(\n (candidate) => candidate.containerId === grantRow.containerId,\n );\n if (!nextGrant || nextGrant.accessLevel !== grantRow.accessLevel) {\n throw new Error(\n `Container ${grantRow.containerId} does not contain the expected group grant`,\n );\n }\n }\n const referencedKeyEpoch = referencedGroupKeyEpoch({\n groupId: input.groupId,\n referencedPrincipalHeads: state.referencedPrincipalHeads,\n });\n if (grant && referencedKeyEpoch === null) {\n throw new Error(\n `Container ${grantRow.containerId} is missing its group reference`,\n );\n }\n return { grant, projection, referencedKeyEpoch };\n}\n\nexport async function buildPrincipalContainerRematerializationBatch(\n input: PrincipalContainerRematerializationInput,\n): Promise<ContainerMutationRequest[]> {\n return (\n await buildPrincipalContainerRematerializationPlans(input)\n ).entries.map((entry) => entry.planned.plan.request);\n}\n\nexport interface PreparedPrincipalContainerRematerializationBatch {\n readonly preceding?: PrincipalRematerializationPredecessor | undefined;\n readonly retiredContainerIds: readonly string[];\n readonly acknowledge: (\n responses: readonly ContainerMutationResponse[],\n stillCurrent?: (() => boolean) | undefined,\n ) => Promise<void>;\n /**\n * Re-sign the batch with the descendant rekeys the server named for its\n * rotations woven in, parent-first, each against the path the batch will\n * leave behind. A named container the batch already rotates is re-planned\n * under what is carried above it, so no container rotates twice. Returns the\n * whole request list, which replaces `requests`; `acknowledge` then expects\n * the responses in that order.\n */\n readonly carry: (\n requiredContainerIds: readonly string[],\n ) => Promise<readonly ContainerMutationRequest[]>;\n readonly plans: readonly MaterializedPrincipalContainerMutationPlan[];\n readonly requests: readonly ContainerMutationRequest[];\n}\n\nfunction authoredMutationHead(\n planned: MaterializedPrincipalContainerMutationPlan,\n): AuthoredContainerMutationHead {\n return planned.plan;\n}\n\nasync function buildPrincipalContainerRematerializationPlan(input: {\n readonly grantRow: PrincipalContainerGrant;\n /** Keys the batch minted above this container; see `BatchPlanning`. */\n readonly knownContainerKeks: ReadonlyMap<string, Uint8Array>;\n /** Holds the policy this batch commits, which rebased paths already cite. */\n readonly principalPolicyCache: PrincipalPolicyCache;\n /** The served projection, re-rooted on any rotation planned above it. */\n readonly projection: ContainerWriterProjectionResponse;\n readonly rematerialization: PrincipalContainerRematerializationInput;\n readonly resolveProjectionUserKey: ReturnType<\n typeof createProjectionUserKeyResolver\n >;\n}): Promise<MaterializedPrincipalContainerMutationPlan> {\n const { grantRow } = input;\n const rematerialization = {\n ...input.rematerialization,\n nextPolicy: await selectReplacementPrincipalPolicyReferences({\n policy: input.rematerialization.nextPolicy,\n projection: input.projection,\n principalPolicyCache: input.principalPolicyCache,\n resolveAuthoredPolicyReferences:\n input.rematerialization.resolveAuthoredPolicyReferences,\n }),\n };\n const { grant, projection, referencedKeyEpoch } =\n await loadGrantedContainerContext(\n rematerialization,\n grantRow,\n input.projection,\n input.principalPolicyCache,\n );\n const nextGrant = rematerialization.nextPolicy.grants.find(\n (candidate) => candidate.containerId === grantRow.containerId,\n );\n const author = {\n ...rematerialization.author,\n organizationId: projection.organizationId,\n };\n const sharedInput = {\n author,\n execSql: rematerialization.execSql,\n knownContainerKeks: input.knownContainerKeks,\n // Planning against a path this batch has yet to commit pins nothing; the\n // acknowledgement advances every head at once. The rekey planner applies\n // this rule itself; revoke and grant planning take it from here.\n persistVerificationCheckpoints:\n !isSpeculativeContainerWriterProjection(projection),\n previousProjection: projection,\n principalPolicyCache: input.principalPolicyCache,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: rematerialization.stillCurrent,\n targetSecretKey: rematerialization.targetSecretKey,\n warmReferencedPrincipalPolicies:\n rematerialization.warmReferencedPrincipalPolicies,\n };\n if (grantRow.containerId === rematerialization.revokedContainerId) {\n if (!grant) {\n throw new Error(\n `Container ${grantRow.containerId} does not contain the revoked group grant`,\n );\n }\n return buildMaterializedContainerRevokePlan({\n ...sharedInput,\n replacementPrincipalPolicy: rematerialization.nextPolicy,\n revokedSubject: {\n subjectId: rematerialization.groupId,\n subjectType: \"group\",\n },\n });\n }\n if (!grant || grant.accessLevel !== nextGrant?.accessLevel) {\n if (!nextGrant) {\n throw new Error(\n `Container ${grantRow.containerId} is absent from the next group grant set`,\n );\n }\n return buildMaterializedContainerSharePlan({\n ...sharedInput,\n accessLevel: nextGrant.accessLevel,\n recipient: {\n principalPolicy: rematerialization.nextPolicy,\n subjectId: rematerialization.groupId,\n subjectType: \"group\",\n },\n });\n }\n if (referencedKeyEpoch === rematerialization.nextPolicy.keyEpoch) {\n return buildMaterializedContainerSharePlan({\n ...sharedInput,\n accessLevel: grant.accessLevel,\n recipient: {\n principalPolicy: rematerialization.nextPolicy,\n subjectId: rematerialization.groupId,\n subjectType: \"group\",\n },\n });\n }\n return buildMaterializedContainerRekeyPlan({\n ...sharedInput,\n replacementPrincipalPolicy: rematerialization.nextPolicy,\n });\n}\n\n/**\n * Plan one target against the paths already planned above it. A grant's\n * rematerialization extends the served head; a rotation's, and every carried\n * rekey, extends the epoch the batch mints above it.\n */\nasync function planRematerializationTarget(\n batch: BatchPlanning,\n target: RematerializationTarget,\n): Promise<void> {\n const { rematerialization } = batch;\n let rebased = rebaseOnDeepestAncestor(target.projection, batch.rotatedAbove);\n const stale = rebased\n ? staleLevelsAbove(\n rebased.projection,\n rebased.ancestor.containerKeks.at(-1)?.containerId ?? \"\",\n )\n : [];\n for (const containerId of stale) {\n await carryLevel(\n batch,\n await loadServedProjection(rematerialization.apiClient, containerId),\n );\n }\n if (!target.grantRow) {\n await carryLevel(batch, target.projection);\n return;\n }\n // Each level carried above re-roots the target one step deeper.\n if (stale.length > 0) {\n rebased = rebaseOnDeepestAncestor(target.projection, batch.rotatedAbove);\n }\n const previousProjection = rebased?.projection ?? target.projection;\n const planned = await buildPrincipalContainerRematerializationPlan({\n grantRow: target.grantRow,\n knownContainerKeks: batch.knownContainerKeks,\n principalPolicyCache: batch.principalPolicyCache,\n projection: previousProjection,\n rematerialization,\n resolveProjectionUserKey: batch.resolveProjectionUserKey,\n });\n const nextProjection =\n \"keyring\" in planned.plan\n ? await containerWriterProjectionFromRotationPlan({\n plan: planned.plan,\n previousProjection,\n })\n : await containerWriterProjectionFromSharePlan({\n plan: planned.plan,\n previousProjection,\n });\n addPlan(batch, {\n carried: false,\n planned,\n rotated: \"keyring\" in planned.plan ? nextProjection : null,\n nextProjection,\n });\n}\n\n/**\n * Sign the batch parent-first. `carriedContainerIds` are the descendant rekeys\n * a refused attempt was told to carry; woven in by depth, each sits under the\n * rotation it rides, and a named container the batch rematerializes anyway is\n * re-planned there rather than rotated twice.\n */\nasync function buildPrincipalContainerRematerializationPlans(\n input: PrincipalContainerRematerializationInput,\n carriedContainerIds: readonly string[] = [],\n): Promise<{\n entries: PlannedRematerialization[];\n retiredContainerIds: readonly string[];\n preceding: PrincipalRematerializationPredecessor;\n}> {\n input = withPrecedingRematerialization(input);\n if (\n input.revokedContainerId &&\n !input.grants.some(\n (grant) => grant.containerId === input.revokedContainerId,\n )\n ) {\n throw new Error(\"Revoked container is not granted to the group\");\n }\n const { targets, retiredContainerIds } = await loadRematerializationTargets({\n apiClient: input.apiClient,\n carriedContainerIds,\n grants: input.grants,\n });\n const batch: BatchPlanning = {\n knownContainerKeks: new Map(input.preceding?.knownContainerKeks),\n plans: [],\n // A rotation planned above cites the group at the head this batch commits,\n // which no store holds yet; the cache is what lets a rebased path verify.\n principalPolicyCache: new Map([\n ...(input.preceding?.policies ?? []).flatMap((policy) => [\n ...seededPrincipalPolicyCache(policy),\n ]),\n ...seededPrincipalPolicyCache(input.nextPolicy),\n ]),\n rematerialization: input,\n resolveProjectionUserKey: createProjectionUserKeyResolver({\n resolveTrustedUserIdentity: input.resolveTrustedUserIdentity,\n }),\n rotatedAbove: [],\n };\n for (const target of targets) {\n await planRematerializationTarget(batch, target);\n }\n return {\n entries: batch.plans,\n retiredContainerIds,\n preceding: {\n policies: [...(input.preceding?.policies ?? []), input.nextPolicy],\n projections: [\n ...(input.preceding?.projections ?? []),\n ...batch.plans.flatMap((entry) =>\n entry.nextProjection ? [entry.nextProjection] : [],\n ),\n ],\n knownContainerKeks: batch.knownContainerKeks,\n },\n };\n}\n\n/**\n * The server names a level it found pinned to a retired epoch. A batch answers\n * with a rotation of it: a carried rekey, or its own rematerialization when\n * that is a rekey or revoke. A rematerialized grant rotates nothing, so a\n * batch that both grants a container and must re-key it cannot be signed;\n * no policy change the SDK builds combines the two.\n */\nfunction assertNamedContainersRotated(\n plans: readonly PlannedRematerialization[],\n requiredContainerIds: readonly string[],\n): void {\n const rotatedIds = new Set(\n plans.flatMap((entry) =>\n entry.rotated ? [entry.planned.plan.containerId] : [],\n ),\n );\n const unrotated = requiredContainerIds\n .slice(0, MAX_ROTATION_CONTAINER_REKEYS)\n .find((containerId) => !rotatedIds.has(containerId));\n if (unrotated !== undefined) {\n throw new Error(\n `Container ${unrotated} needs a rekey this policy change only grants`,\n );\n }\n}\n\nexport async function preparePrincipalContainerRematerializationBatch(\n input: PrincipalContainerRematerializationInput,\n): Promise<PreparedPrincipalContainerRematerializationBatch> {\n let { entries, retiredContainerIds, preceding } =\n await buildPrincipalContainerRematerializationPlans(input);\n const requests = () => entries.map((entry) => entry.planned.plan.request);\n return {\n get preceding() {\n return preceding;\n },\n get retiredContainerIds() {\n return retiredContainerIds;\n },\n get plans() {\n return entries.map((entry) => entry.planned);\n },\n get requests() {\n return requests();\n },\n carry: async (requiredContainerIds) => {\n // The refused attempt rolled back whole, so every served head still\n // stands; the batch is signed again from them with the named levels in.\n const replanned = await buildPrincipalContainerRematerializationPlans(\n input,\n requiredContainerIds,\n );\n assertNamedContainersRotated(replanned.entries, requiredContainerIds);\n entries = replanned.entries;\n retiredContainerIds = replanned.retiredContainerIds;\n preceding = replanned.preceding;\n return requests();\n },\n acknowledge: async (responses, stillCurrent) => {\n const isCurrent = () =>\n input.stillCurrent?.() !== false && stillCurrent?.() !== false;\n const heads = entries.map((entry) => authoredMutationHead(entry.planned));\n const acknowledged = await acknowledgeContainerMutationBatch({\n execSql: input.execSql,\n plans: heads,\n responses,\n stillCurrent: isCurrent,\n });\n if (!acknowledged || !isCurrent()) return;\n const plansByOrganization = new Map<\n string,\n AuthoredContainerMutationHead[]\n >();\n for (const head of heads) {\n const organizationId = head.state.organizationId;\n const group = plansByOrganization.get(organizationId) ?? [];\n group.push(head);\n plansByOrganization.set(organizationId, group);\n }\n for (const [organizationId, organizationPlans] of plansByOrganization) {\n try {\n rememberVerifiedContainerHeads({\n organizationId,\n execSql: input.execSql,\n heads: [],\n policies: [input.nextPolicy],\n });\n } catch {\n // Cache failure must not invalidate the durably acknowledged batch.\n continue;\n }\n scheduleHeldDescendantRecitations({\n apiClient: input.apiClient,\n author: { ...input.author, organizationId },\n execSql: input.execSql,\n plans: organizationPlans,\n stillCurrent: input.recitationStillCurrent ?? isCurrent,\n reportSecurityIncident: input.reportSecurityIncident,\n });\n }\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"principalContainerRematerialization.js","sourceRoot":"","sources":["principalContainerRematerialization.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ContainerDirectGrant,\n PrincipalContainerGrant,\n} from \"@tearleads/crypto\";\nimport type { ContainerMutationRequest } from \"@tearleads/validators/request\";\nimport type {\n ContainerMutationResponse,\n ContainerWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { rememberVerifiedContainerHeads } from \"../../../data/containers/shared/heldContainerHeads\";\nimport type { AuthoredContainerMutationHead } from \"../../../data/containers/shared/mutationAcknowledgement\";\nimport { acknowledgeContainerMutationBatch } from \"../../../data/containers/shared/mutationAcknowledgement\";\nimport {\n getTargetContainerContext,\n readContainerState,\n} from \"../../../data/containers/shared/projection\";\nimport {\n type PrincipalPolicyCache,\n verifyContainerWriterProjection,\n} from \"../../../data/keyingProjectionVerification\";\nimport { createProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification/userKeyResolver\";\nimport { scheduleHeldDescendantRecitations } from \"../child/recite\";\nimport { buildMaterializedContainerRekeyPlan } from \"../child/rekey\";\nimport { rebaseOnDeepestAncestor } from \"../child/rekey/carriedDescendantRekeys\";\nimport {\n containerWriterProjectionFromRotationPlan,\n containerWriterProjectionFromSharePlan,\n isSpeculativeContainerWriterProjection,\n} from \"../child/rekey/rekeyProjection\";\nimport { buildMaterializedContainerRevokePlan } from \"../child/revoke\";\nimport { selectReplacementPrincipalPolicyReferences } from \"../child/rotation/replacementPrincipalPolicyReferences\";\nimport { buildMaterializedContainerSharePlan } from \"../child/share/shareMaterialization\";\nimport { withPrecedingRematerialization } from \"./precedingPrincipalRematerialization\";\nimport {\n addPlan,\n assertNamedContainersRotated,\n type BatchPlanning,\n carryLevel,\n type PrincipalContainerRematerializationInput,\n type PrincipalRematerializationPredecessor,\n planOnceMoreAfterRollback,\n rerootOnAuthoredPaths,\n staleLevelsAbove,\n} from \"./principalContainerRematerializationPlanning\";\nimport {\n loadRematerializationTargets,\n loadServedProjection,\n type MaterializedPrincipalContainerMutationPlan,\n type PlannedRematerialization,\n type RematerializationTarget,\n seededPrincipalPolicyCache,\n} from \"./principalContainerRematerializationTargets\";\n\nfunction matchingGroupGrant(input: {\n readonly directGrants: readonly ContainerDirectGrant[];\n readonly groupId: string;\n}): ContainerDirectGrant | null {\n return (\n input.directGrants.find(\n (grant) =>\n grant.subjectType === \"group\" && grant.subjectId === input.groupId,\n ) ?? null\n );\n}\n\nfunction referencedGroupKeyEpoch(input: {\n readonly groupId: string;\n readonly referencedPrincipalHeads: ReturnType<\n typeof readContainerState\n >[\"referencedPrincipalHeads\"];\n}): number | null {\n return (\n input.referencedPrincipalHeads.find(\n (head) =>\n head.principalType === \"group\" && head.principalId === input.groupId,\n )?.keyEpoch ?? null\n );\n}\n\nasync function loadGrantedContainerContext(\n input: PrincipalContainerRematerializationInput,\n grantRow: PrincipalContainerGrant,\n projection: ContainerWriterProjectionResponse,\n principalPolicyCache: PrincipalPolicyCache,\n) {\n // A projection re-rooted on a rotation this batch has yet to commit must\n // not pin anything: its heads are speculative until acknowledged, and a\n // pin past the group's served head would refuse the other served paths.\n await verifyContainerWriterProjection({\n execSql: input.execSql,\n persistVerificationCheckpoints:\n !isSpeculativeContainerWriterProjection(projection),\n principalPolicyCache,\n projection,\n resolveUserKey: createProjectionUserKeyResolver({\n resolveTrustedUserIdentity: input.resolveTrustedUserIdentity,\n }),\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n const state = readContainerState(\n getTargetContainerContext(projection).manifest,\n );\n const grant = matchingGroupGrant({\n directGrants: state.directGrants,\n groupId: input.groupId,\n });\n if (grant && grant.accessLevel !== grantRow.accessLevel) {\n const nextGrant = input.nextPolicy.grants.find(\n (candidate) => candidate.containerId === grantRow.containerId,\n );\n if (!nextGrant || nextGrant.accessLevel !== grantRow.accessLevel) {\n throw new Error(\n `Container ${grantRow.containerId} does not contain the expected group grant`,\n );\n }\n }\n const referencedKeyEpoch = referencedGroupKeyEpoch({\n groupId: input.groupId,\n referencedPrincipalHeads: state.referencedPrincipalHeads,\n });\n if (grant && referencedKeyEpoch === null) {\n throw new Error(\n `Container ${grantRow.containerId} is missing its group reference`,\n );\n }\n return { grant, projection, referencedKeyEpoch };\n}\n\nexport async function buildPrincipalContainerRematerializationBatch(\n input: PrincipalContainerRematerializationInput,\n): Promise<ContainerMutationRequest[]> {\n return (\n await buildPrincipalContainerRematerializationPlans(input)\n ).entries.map((entry) => entry.planned.plan.request);\n}\n\nexport interface PreparedPrincipalContainerRematerializationBatch {\n readonly preceding?: PrincipalRematerializationPredecessor | undefined;\n readonly retiredContainerIds: readonly string[];\n readonly acknowledge: (\n responses: readonly ContainerMutationResponse[],\n stillCurrent?: (() => boolean) | undefined,\n ) => Promise<void>;\n /**\n * Re-sign the batch with the descendant rekeys the server named for its\n * rotations woven in, parent-first, each against the path the batch will\n * leave behind. A named container the batch already rotates is re-planned\n * under what is carried above it, so no container rotates twice. Returns the\n * whole request list, which replaces `requests`; `acknowledge` then expects\n * the responses in that order.\n */\n readonly carry: (\n requiredContainerIds: readonly string[],\n ) => Promise<readonly ContainerMutationRequest[]>;\n readonly plans: readonly MaterializedPrincipalContainerMutationPlan[];\n readonly requests: readonly ContainerMutationRequest[];\n}\n\nfunction authoredMutationHead(\n planned: MaterializedPrincipalContainerMutationPlan,\n): AuthoredContainerMutationHead {\n return planned.plan;\n}\n\nasync function buildPrincipalContainerRematerializationPlan(input: {\n readonly grantRow: PrincipalContainerGrant;\n /** Keys the batch minted above this container; see `BatchPlanning`. */\n readonly knownContainerKeks: ReadonlyMap<string, Uint8Array>;\n /** Holds the policy this batch commits, which rebased paths already cite. */\n readonly principalPolicyCache: PrincipalPolicyCache;\n /** The served projection, re-rooted on any rotation planned above it. */\n readonly projection: ContainerWriterProjectionResponse;\n readonly rematerialization: PrincipalContainerRematerializationInput;\n readonly resolveProjectionUserKey: ReturnType<\n typeof createProjectionUserKeyResolver\n >;\n}): Promise<MaterializedPrincipalContainerMutationPlan> {\n const { grantRow } = input;\n const rematerialization = {\n ...input.rematerialization,\n nextPolicy: await selectReplacementPrincipalPolicyReferences({\n policy: input.rematerialization.nextPolicy,\n projection: input.projection,\n principalPolicyCache: input.principalPolicyCache,\n resolveAuthoredPolicyReferences:\n input.rematerialization.resolveAuthoredPolicyReferences,\n }),\n };\n const { grant, projection, referencedKeyEpoch } =\n await loadGrantedContainerContext(\n rematerialization,\n grantRow,\n input.projection,\n input.principalPolicyCache,\n );\n const nextGrant = rematerialization.nextPolicy.grants.find(\n (candidate) => candidate.containerId === grantRow.containerId,\n );\n const author = {\n ...rematerialization.author,\n organizationId: projection.organizationId,\n };\n const sharedInput = {\n author,\n execSql: rematerialization.execSql,\n knownContainerKeks: input.knownContainerKeks,\n // Planning against a path this batch has yet to commit pins nothing; the\n // acknowledgement advances every head at once. The rekey planner applies\n // this rule itself; revoke and grant planning take it from here.\n persistVerificationCheckpoints:\n !isSpeculativeContainerWriterProjection(projection),\n previousProjection: projection,\n principalPolicyCache: input.principalPolicyCache,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: rematerialization.stillCurrent,\n targetSecretKey: rematerialization.targetSecretKey,\n warmReferencedPrincipalPolicies:\n rematerialization.warmReferencedPrincipalPolicies,\n };\n if (grantRow.containerId === rematerialization.revokedContainerId) {\n if (!grant) {\n throw new Error(\n `Container ${grantRow.containerId} does not contain the revoked group grant`,\n );\n }\n return buildMaterializedContainerRevokePlan({\n ...sharedInput,\n replacementPrincipalPolicy: rematerialization.nextPolicy,\n revokedSubject: {\n subjectId: rematerialization.groupId,\n subjectType: \"group\",\n },\n });\n }\n if (!grant || grant.accessLevel !== nextGrant?.accessLevel) {\n if (!nextGrant) {\n throw new Error(\n `Container ${grantRow.containerId} is absent from the next group grant set`,\n );\n }\n return buildMaterializedContainerSharePlan({\n ...sharedInput,\n accessLevel: nextGrant.accessLevel,\n recipient: {\n principalPolicy: rematerialization.nextPolicy,\n subjectId: rematerialization.groupId,\n subjectType: \"group\",\n },\n });\n }\n if (referencedKeyEpoch === rematerialization.nextPolicy.keyEpoch) {\n return buildMaterializedContainerSharePlan({\n ...sharedInput,\n accessLevel: grant.accessLevel,\n recipient: {\n principalPolicy: rematerialization.nextPolicy,\n subjectId: rematerialization.groupId,\n subjectType: \"group\",\n },\n });\n }\n return buildMaterializedContainerRekeyPlan({\n ...sharedInput,\n replacementPrincipalPolicy: rematerialization.nextPolicy,\n });\n}\n\n/**\n * Plan one target against the paths already planned above it. A grant's\n * rematerialization extends the served head; a rotation's, and every carried\n * rekey, extends the epoch the batch mints above it.\n */\nasync function planRematerializationTarget(\n batch: BatchPlanning,\n target: RematerializationTarget,\n): Promise<void> {\n const { rematerialization } = batch;\n const rebased = rebaseOnDeepestAncestor(\n target.projection,\n batch.rotatedAbove,\n );\n const stale = rebased\n ? staleLevelsAbove(\n rebased.projection,\n rebased.ancestor.containerKeks.at(-1)?.containerId ?? \"\",\n )\n : [];\n for (const containerId of stale) {\n await carryLevel(\n batch,\n await loadServedProjection(rematerialization.apiClient, containerId),\n );\n }\n if (!target.grantRow) {\n await carryLevel(batch, target.projection);\n return;\n }\n // Each level carried above re-roots the target one step deeper.\n const previousProjection = await rerootOnAuthoredPaths(\n batch,\n target.projection,\n );\n const planned = await buildPrincipalContainerRematerializationPlan({\n grantRow: target.grantRow,\n knownContainerKeks: batch.knownContainerKeks,\n principalPolicyCache: batch.principalPolicyCache,\n projection: previousProjection,\n rematerialization,\n resolveProjectionUserKey: batch.resolveProjectionUserKey,\n });\n const nextProjection =\n \"keyring\" in planned.plan\n ? await containerWriterProjectionFromRotationPlan({\n plan: planned.plan,\n previousProjection,\n })\n : await containerWriterProjectionFromSharePlan({\n plan: planned.plan,\n previousProjection,\n });\n addPlan(batch, {\n carried: false,\n planned,\n rotated: \"keyring\" in planned.plan ? nextProjection : null,\n nextProjection,\n });\n}\n\n/**\n * Sign the batch parent-first. `carriedContainerIds` are the descendant rekeys\n * a refused attempt was told to carry; woven in by depth, each sits under the\n * rotation it rides, and a named container the batch rematerializes anyway is\n * re-planned there rather than rotated twice.\n */\nfunction buildPrincipalContainerRematerializationPlans(\n input: PrincipalContainerRematerializationInput,\n carriedContainerIds: readonly string[] = [],\n) {\n return planOnceMoreAfterRollback(\n input.apiClient,\n [...input.grants.map((grant) => grant.containerId), ...carriedContainerIds],\n () => planRematerializationBatch(input, carriedContainerIds),\n );\n}\n\nasync function planRematerializationBatch(\n input: PrincipalContainerRematerializationInput,\n carriedContainerIds: readonly string[],\n): Promise<{\n entries: PlannedRematerialization[];\n retiredContainerIds: readonly string[];\n preceding: PrincipalRematerializationPredecessor;\n}> {\n input = withPrecedingRematerialization(input);\n if (\n input.revokedContainerId &&\n !input.grants.some(\n (grant) => grant.containerId === input.revokedContainerId,\n )\n ) {\n throw new Error(\"Revoked container is not granted to the group\");\n }\n const { targets, retiredContainerIds } = await loadRematerializationTargets({\n apiClient: input.apiClient,\n carriedContainerIds,\n grants: input.grants,\n });\n const batch: BatchPlanning = {\n knownContainerKeks: new Map(input.preceding?.knownContainerKeks),\n plans: [],\n // A rotation planned above cites the group at the head this batch commits,\n // which no store holds yet; the cache is what lets a rebased path verify.\n principalPolicyCache: new Map([\n ...(input.preceding?.policies ?? []).flatMap((policy) => [\n ...seededPrincipalPolicyCache(policy),\n ]),\n ...seededPrincipalPolicyCache(input.nextPolicy),\n ]),\n rematerialization: input,\n resolveProjectionUserKey: createProjectionUserKeyResolver({\n resolveTrustedUserIdentity: input.resolveTrustedUserIdentity,\n }),\n rotatedAbove: [],\n authoredAbove: [],\n };\n for (const target of targets) {\n await planRematerializationTarget(batch, target);\n }\n return {\n entries: batch.plans,\n retiredContainerIds,\n preceding: {\n policies: [...(input.preceding?.policies ?? []), input.nextPolicy],\n projections: [\n ...(input.preceding?.projections ?? []),\n ...batch.plans.flatMap((entry) =>\n entry.nextProjection ? [entry.nextProjection] : [],\n ),\n ],\n knownContainerKeks: batch.knownContainerKeks,\n },\n };\n}\n\nexport async function preparePrincipalContainerRematerializationBatch(\n input: PrincipalContainerRematerializationInput,\n): Promise<PreparedPrincipalContainerRematerializationBatch> {\n let { entries, retiredContainerIds, preceding } =\n await buildPrincipalContainerRematerializationPlans(input);\n const requests = () => entries.map((entry) => entry.planned.plan.request);\n return {\n get preceding() {\n return preceding;\n },\n get retiredContainerIds() {\n return retiredContainerIds;\n },\n get plans() {\n return entries.map((entry) => entry.planned);\n },\n get requests() {\n return requests();\n },\n carry: async (requiredContainerIds) => {\n // The refused attempt rolled back whole, so every served head still\n // stands; the batch is signed again from them with the named levels in.\n const replanned = await buildPrincipalContainerRematerializationPlans(\n input,\n requiredContainerIds,\n );\n assertNamedContainersRotated(replanned.entries, requiredContainerIds);\n entries = replanned.entries;\n retiredContainerIds = replanned.retiredContainerIds;\n preceding = replanned.preceding;\n return requests();\n },\n acknowledge: async (responses, stillCurrent) => {\n const isCurrent = () =>\n input.stillCurrent?.() !== false && stillCurrent?.() !== false;\n const heads = entries.map((entry) => authoredMutationHead(entry.planned));\n const acknowledged = await acknowledgeContainerMutationBatch({\n execSql: input.execSql,\n plans: heads,\n responses,\n stillCurrent: isCurrent,\n });\n if (!acknowledged || !isCurrent()) return;\n const plansByOrganization = new Map<\n string,\n AuthoredContainerMutationHead[]\n >();\n for (const head of heads) {\n const organizationId = head.state.organizationId;\n const group = plansByOrganization.get(organizationId) ?? [];\n group.push(head);\n plansByOrganization.set(organizationId, group);\n }\n for (const [organizationId, organizationPlans] of plansByOrganization) {\n try {\n rememberVerifiedContainerHeads({\n organizationId,\n execSql: input.execSql,\n heads: [],\n policies: [input.nextPolicy],\n });\n } catch {\n // Cache failure must not invalidate the durably acknowledged batch.\n continue;\n }\n scheduleHeldDescendantRecitations({\n apiClient: input.apiClient,\n author: { ...input.author, organizationId },\n execSql: input.execSql,\n plans: organizationPlans,\n stillCurrent: input.recitationStillCurrent ?? isCurrent,\n reportSecurityIncident: input.reportSecurityIncident,\n });\n }\n },\n };\n}\n"]}
|
package/workflows/containers/rematerialization/principalContainerRematerializationPlanning.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { KeyingVerificationError, } from "../../../internal/crypto/index.js";
|
|
1
2
|
import { MAX_ROTATION_CONTAINER_REKEYS } from "../../../internal/validators/util/index.js";
|
|
2
|
-
import {
|
|
3
|
+
import { verifyContainerWriterProjection, } from "../../../data/keyingProjectionVerification/index.js";
|
|
4
|
+
import { planCarriedDescendantRekey, rebaseOnDeepestAncestor, } from "../child/rekey/carriedDescendantRekeys.js";
|
|
5
|
+
import { isSpeculativeContainerWriterProjection } from "../child/rekey/rekeyProjection.js";
|
|
3
6
|
/** How many rekeys the batch carries beyond its rematerializations. */
|
|
4
7
|
function carriedCount(batch) {
|
|
5
8
|
return batch.plans.filter((entry) => entry.carried).length;
|
|
@@ -42,8 +45,39 @@ export async function carryLevel(batch, served) {
|
|
|
42
45
|
nextProjection: planned.writerProjection,
|
|
43
46
|
});
|
|
44
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Re-root a granted target on the deepest path the batch authored above it, a
|
|
50
|
+
* grant's included: the server applies the batch in order, so the target
|
|
51
|
+
* cites every head an earlier entry advances. Its served head is pinned first,
|
|
52
|
+
* as a carried rekey's is. The batch is acknowledged against the latest
|
|
53
|
+
* durable pin, and a re-rooted path pins nothing, so a device that never
|
|
54
|
+
* pinned this container would refuse its own batch once the server took it.
|
|
55
|
+
* Only what the API served is pinned: in a compound transaction the target
|
|
56
|
+
* may already sit on the preceding batch's heads, which a refused commit
|
|
57
|
+
* never makes real.
|
|
58
|
+
*/
|
|
59
|
+
export async function rerootOnAuthoredPaths(batch, served) {
|
|
60
|
+
const rebased = rebaseOnDeepestAncestor(served, batch.authoredAbove);
|
|
61
|
+
if (!rebased)
|
|
62
|
+
return served;
|
|
63
|
+
if (isSpeculativeContainerWriterProjection(served)) {
|
|
64
|
+
return rebased.projection;
|
|
65
|
+
}
|
|
66
|
+
const { rematerialization } = batch;
|
|
67
|
+
await verifyContainerWriterProjection({
|
|
68
|
+
execSql: rematerialization.execSql,
|
|
69
|
+
principalPolicyCache: batch.principalPolicyCache,
|
|
70
|
+
projection: served,
|
|
71
|
+
resolveUserKey: batch.resolveProjectionUserKey,
|
|
72
|
+
stillCurrent: rematerialization.stillCurrent,
|
|
73
|
+
warmReferencedPrincipalPolicies: rematerialization.warmReferencedPrincipalPolicies,
|
|
74
|
+
});
|
|
75
|
+
return rebased.projection;
|
|
76
|
+
}
|
|
45
77
|
export function addPlan(batch, entry) {
|
|
46
78
|
batch.plans.push(entry);
|
|
79
|
+
if (entry.nextProjection)
|
|
80
|
+
batch.authoredAbove.push(entry.nextProjection);
|
|
47
81
|
if (!entry.rotated)
|
|
48
82
|
return;
|
|
49
83
|
batch.rotatedAbove.push(entry.rotated);
|
|
@@ -74,4 +108,41 @@ export function staleLevelsAbove(projection, rotatedAncestorId) {
|
|
|
74
108
|
? []
|
|
75
109
|
: between.slice(firstStale).map((kek) => kek.containerId);
|
|
76
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* The server names a level it found pinned to a retired epoch. A batch answers
|
|
113
|
+
* with a rotation of it: a carried rekey, or its own rematerialization when
|
|
114
|
+
* that is a rekey or revoke. A rematerialized grant rotates nothing, so a
|
|
115
|
+
* batch that both grants a container and must re-key it cannot be signed;
|
|
116
|
+
* no policy change the SDK builds combines the two.
|
|
117
|
+
*/
|
|
118
|
+
export function assertNamedContainersRotated(plans, requiredContainerIds) {
|
|
119
|
+
const rotatedIds = new Set(plans.flatMap((entry) => entry.rotated ? [entry.planned.plan.containerId] : []));
|
|
120
|
+
const unrotated = requiredContainerIds
|
|
121
|
+
.slice(0, MAX_ROTATION_CONTAINER_REKEYS)
|
|
122
|
+
.find((containerId) => !rotatedIds.has(containerId));
|
|
123
|
+
if (unrotated !== undefined) {
|
|
124
|
+
throw new Error(`Container ${unrotated} needs a rekey this policy change only grants`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Plan once more after a rollback. A batch reads the served heads of what it
|
|
129
|
+
* signs for, then verifies them, and this device's own re-citation of held
|
|
130
|
+
* descendants, scheduled by a share above, may pin a newer head in between
|
|
131
|
+
* (#2647). The second plan reads fresh heads held to the same pins, so a
|
|
132
|
+
* server that serves a rolled-back head again still fails the batch.
|
|
133
|
+
*/
|
|
134
|
+
export async function planOnceMoreAfterRollback(apiClient, containerIds, plan) {
|
|
135
|
+
try {
|
|
136
|
+
return await plan();
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (!(error instanceof KeyingVerificationError) ||
|
|
140
|
+
error.code !== "rollback")
|
|
141
|
+
throw error;
|
|
142
|
+
for (const containerId of containerIds) {
|
|
143
|
+
apiClient.evictContainerWriterProjection?.(containerId);
|
|
144
|
+
}
|
|
145
|
+
return plan();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
77
148
|
//# sourceMappingURL=principalContainerRematerializationPlanning.js.map
|
package/workflows/containers/rematerialization/principalContainerRematerializationPlanning.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"principalContainerRematerializationPlanning.js","sourceRoot":"","sources":["principalContainerRematerializationPlanning.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ApiClient } from \"@tearleads/api-client\";\nimport type { PrincipalContainerGrant } from \"@tearleads/crypto\";\nimport type { ContainerWriterProjectionResponse } from \"@tearleads/validators/response\";\nimport { MAX_ROTATION_CONTAINER_REKEYS } from \"@tearleads/validators/util\";\nimport type { ContainerReciteApi } from \"../../../data/containers/shared/reciteApi\";\nimport type { ContainerMutationAuthor } from \"../../../data/containers/shared/types\";\nimport type {\n PrincipalPolicyCache,\n ReferencedPrincipalPolicyWarmer,\n} from \"../../../data/keyingProjectionVerification\";\nimport type { ResolvedPrincipalPolicyEvidence } from \"../../../data/keyingProjectionVerification/types\";\nimport type { createProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification/userKeyResolver\";\nimport type { CurrentPolicyReferenceResolver } from \"../../../data/principals/currentPolicyReferenceResolver\";\nimport type { PrincipalPolicyCurrentEvidence } from \"../../../data/principals/principalPolicyEvidence\";\nimport type { SecurityIncidentReporter } from \"../../../data/shared/securityIncidents\";\nimport type { ExecSql } from \"../../../data/sqlite/sqlSchema\";\nimport type { TrustedUserIdentityResolver } from \"../../../data/trustedUserIdentity\";\nimport {\n planCarriedDescendantRekey,\n type SpeculativePath,\n} from \"../child/rekey/carriedDescendantRekeys\";\nimport type { PlannedRematerialization } from \"./principalContainerRematerializationTargets\";\n\nexport interface RematerializationApi\n extends ContainerReciteApi,\n Pick<ApiClient, \"getContainerWriterProjectionResult\"> {\n getContainerWriterProjection(\n containerId: string,\n ): Promise<ContainerWriterProjectionResponse | null>;\n}\n\nexport interface PrincipalRematerializationPredecessor {\n readonly authored?: readonly ResolvedPrincipalPolicyEvidence[] | undefined;\n readonly policies: readonly PrincipalPolicyCurrentEvidence[];\n readonly projections: readonly ContainerWriterProjectionResponse[];\n readonly knownContainerKeks: ReadonlyMap<string, Uint8Array>;\n}\n\nexport interface PrincipalContainerRematerializationInput {\n readonly preceding?: PrincipalRematerializationPredecessor | undefined;\n readonly reportSecurityIncident: SecurityIncidentReporter;\n readonly apiClient: RematerializationApi;\n readonly author: ContainerMutationAuthor;\n readonly execSql: ExecSql;\n readonly grants: readonly PrincipalContainerGrant[];\n readonly groupId: string;\n readonly nextPolicy: PrincipalPolicyCurrentEvidence;\n readonly resolveAuthoredPolicyReferences?:\n | CurrentPolicyReferenceResolver\n | undefined;\n readonly revokedContainerId?: string | undefined;\n readonly resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n readonly stillCurrent?: (() => boolean) | undefined;\n /** Session lifetime for best-effort work after durable acknowledgement. */\n readonly recitationStillCurrent?: (() => boolean) | undefined;\n readonly targetSecretKey: Uint8Array;\n readonly warmReferencedPrincipalPolicies?:\n | ReferencedPrincipalPolicyWarmer\n | undefined;\n}\n\n/** What the batch has signed so far, in the order it will be submitted. */\nexport interface BatchPlanning {\n /**\n * Keys the batch minted, by epoch id. A rotation wraps its successor to the\n * group at the head this batch commits, which no store can open yet; a\n * level planned beneath it opens that epoch with the key minted here, and\n * its keyring then opens the retired epoch the level below is pinned to.\n */\n readonly knownContainerKeks: Map<string, Uint8Array>;\n readonly plans: PlannedRematerialization[];\n readonly principalPolicyCache: PrincipalPolicyCache;\n readonly rematerialization: PrincipalContainerRematerializationInput;\n readonly resolveProjectionUserKey: ReturnType<\n typeof createProjectionUserKeyResolver\n >;\n readonly rotatedAbove: SpeculativePath[];\n}\n\n/** How many rekeys the batch carries beyond its rematerializations. */\nfunction carriedCount(batch: BatchPlanning): number {\n return batch.plans.filter((entry) => entry.carried).length;\n}\n\n/**\n * Sign a rekey of `served` under the batch's rotations above it, and add it.\n * The server accepts at most `MAX_ROTATION_CONTAINER_REKEYS` carried entries;\n * past that a rematerialization below them could not be signed on a current\n * path, so the batch is refused here, before signing what the server would\n * refuse anyway, and the policy change has to wait for a lazy repair of the\n * tree. Groups granted that far apart on one chain are not a shape the SDK\n * creates.\n */\nexport async function carryLevel(\n batch: BatchPlanning,\n served: ContainerWriterProjectionResponse,\n): Promise<void> {\n const { rematerialization } = batch;\n if (carriedCount(batch) >= MAX_ROTATION_CONTAINER_REKEYS) {\n throw new Error(\n `Policy change would carry more than ${MAX_ROTATION_CONTAINER_REKEYS} descendant rekeys`,\n );\n }\n const planned = await planCarriedDescendantRekey({\n apiClient: rematerialization.apiClient,\n author: rematerialization.author,\n execSql: rematerialization.execSql,\n knownContainerKeks: batch.knownContainerKeks,\n principalPolicyCache: batch.principalPolicyCache,\n // Its path cites the policy this batch commits, so like every\n // rematerialized rotation it must cite the successor.\n replacementPrincipalPolicy: rematerialization.nextPolicy,\n resolveAuthoredPolicyReferences:\n rematerialization.resolveAuthoredPolicyReferences,\n resolveProjectionUserKey: batch.resolveProjectionUserKey,\n rotated: batch.rotatedAbove,\n served,\n stillCurrent: rematerialization.stillCurrent,\n targetSecretKey: rematerialization.targetSecretKey,\n warmReferencedPrincipalPolicies:\n rematerialization.warmReferencedPrincipalPolicies,\n });\n addPlan(batch, {\n carried: true,\n planned,\n rotated: planned.writerProjection,\n nextProjection: planned.writerProjection,\n });\n}\n\nexport function addPlan(\n batch: BatchPlanning,\n entry: PlannedRematerialization,\n): void {\n batch.plans.push(entry);\n if (!entry.rotated) return;\n batch.rotatedAbove.push(entry.rotated);\n const minted = entry.rotated.containerKeks.at(-1)?.containerKeyEpochId;\n if (minted) batch.knownContainerKeks.set(minted, entry.planned.containerKey);\n}\n\n/**\n * Levels of a re-rooted path pinned to an epoch the batch retires above them:\n * those strictly between the rotated ancestor the path was re-rooted on and\n * the target, parent-first, from the first stale one down. A rotation planned\n * above a target strands every level between the two, and each must be\n * re-keyed under the one above before the target can be signed. The batch\n * carries them without waiting to be told: they are proper ancestors of a\n * granted container, so the server would name them, and a refusal per level\n * is a round trip each. Anything stale above the rotated ancestor is not this\n * batch's doing and is left alone. `carryLevel` holds the count to the cap.\n */\nexport function staleLevelsAbove(\n projection: ContainerWriterProjectionResponse,\n rotatedAncestorId: string,\n): string[] {\n const keks = projection.containerKeks;\n const ancestor = keks.findIndex(\n (kek) => kek.containerId === rotatedAncestorId,\n );\n if (ancestor < 0) return [];\n const between = keks.slice(ancestor, -1);\n const firstStale = between.findIndex(\n (kek, index) =>\n index > 0 &&\n kek.parentContainerKeyEpochId !== between[index - 1]?.containerKeyEpochId,\n );\n return firstStale < 0\n ? []\n : between.slice(firstStale).map((kek) => kek.containerId);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"principalContainerRematerializationPlanning.js","sourceRoot":"","sources":["principalContainerRematerializationPlanning.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ApiClient } from \"@tearleads/api-client\";\nimport {\n KeyingVerificationError,\n type PrincipalContainerGrant,\n} from \"@tearleads/crypto\";\nimport type { ContainerWriterProjectionResponse } from \"@tearleads/validators/response\";\nimport { MAX_ROTATION_CONTAINER_REKEYS } from \"@tearleads/validators/util\";\nimport type { ContainerReciteApi } from \"../../../data/containers/shared/reciteApi\";\nimport type { ContainerMutationAuthor } from \"../../../data/containers/shared/types\";\nimport {\n type PrincipalPolicyCache,\n type ReferencedPrincipalPolicyWarmer,\n verifyContainerWriterProjection,\n} from \"../../../data/keyingProjectionVerification\";\nimport type { ResolvedPrincipalPolicyEvidence } from \"../../../data/keyingProjectionVerification/types\";\nimport type { createProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification/userKeyResolver\";\nimport type { CurrentPolicyReferenceResolver } from \"../../../data/principals/currentPolicyReferenceResolver\";\nimport type { PrincipalPolicyCurrentEvidence } from \"../../../data/principals/principalPolicyEvidence\";\nimport type { SecurityIncidentReporter } from \"../../../data/shared/securityIncidents\";\nimport type { ExecSql } from \"../../../data/sqlite/sqlSchema\";\nimport type { TrustedUserIdentityResolver } from \"../../../data/trustedUserIdentity\";\nimport {\n planCarriedDescendantRekey,\n rebaseOnDeepestAncestor,\n type SpeculativePath,\n} from \"../child/rekey/carriedDescendantRekeys\";\nimport { isSpeculativeContainerWriterProjection } from \"../child/rekey/rekeyProjection\";\nimport type { PlannedRematerialization } from \"./principalContainerRematerializationTargets\";\n\nexport interface RematerializationApi\n extends ContainerReciteApi,\n Pick<ApiClient, \"getContainerWriterProjectionResult\"> {\n getContainerWriterProjection(\n containerId: string,\n ): Promise<ContainerWriterProjectionResponse | null>;\n evictContainerWriterProjection?(containerId: string): void;\n}\n\nexport interface PrincipalRematerializationPredecessor {\n readonly authored?: readonly ResolvedPrincipalPolicyEvidence[] | undefined;\n readonly policies: readonly PrincipalPolicyCurrentEvidence[];\n readonly projections: readonly ContainerWriterProjectionResponse[];\n readonly knownContainerKeks: ReadonlyMap<string, Uint8Array>;\n}\n\nexport interface PrincipalContainerRematerializationInput {\n readonly preceding?: PrincipalRematerializationPredecessor | undefined;\n readonly reportSecurityIncident: SecurityIncidentReporter;\n readonly apiClient: RematerializationApi;\n readonly author: ContainerMutationAuthor;\n readonly execSql: ExecSql;\n readonly grants: readonly PrincipalContainerGrant[];\n readonly groupId: string;\n readonly nextPolicy: PrincipalPolicyCurrentEvidence;\n readonly resolveAuthoredPolicyReferences?:\n | CurrentPolicyReferenceResolver\n | undefined;\n readonly revokedContainerId?: string | undefined;\n readonly resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n readonly stillCurrent?: (() => boolean) | undefined;\n /** Session lifetime for best-effort work after durable acknowledgement. */\n readonly recitationStillCurrent?: (() => boolean) | undefined;\n readonly targetSecretKey: Uint8Array;\n readonly warmReferencedPrincipalPolicies?:\n | ReferencedPrincipalPolicyWarmer\n | undefined;\n}\n\n/** What the batch has signed so far, in the order it will be submitted. */\nexport interface BatchPlanning {\n /**\n * Keys the batch minted, by epoch id. A rotation wraps its successor to the\n * group at the head this batch commits, which no store can open yet; a\n * level planned beneath it opens that epoch with the key minted here, and\n * its keyring then opens the retired epoch the level below is pinned to.\n */\n readonly knownContainerKeks: Map<string, Uint8Array>;\n readonly plans: PlannedRematerialization[];\n readonly principalPolicyCache: PrincipalPolicyCache;\n readonly rematerialization: PrincipalContainerRematerializationInput;\n readonly resolveProjectionUserKey: ReturnType<\n typeof createProjectionUserKeyResolver\n >;\n readonly rotatedAbove: SpeculativePath[];\n /** Every path the batch authored, a grant's included, in signing order. */\n readonly authoredAbove: SpeculativePath[];\n}\n\n/** How many rekeys the batch carries beyond its rematerializations. */\nfunction carriedCount(batch: BatchPlanning): number {\n return batch.plans.filter((entry) => entry.carried).length;\n}\n\n/**\n * Sign a rekey of `served` under the batch's rotations above it, and add it.\n * The server accepts at most `MAX_ROTATION_CONTAINER_REKEYS` carried entries;\n * past that a rematerialization below them could not be signed on a current\n * path, so the batch is refused here, before signing what the server would\n * refuse anyway, and the policy change has to wait for a lazy repair of the\n * tree. Groups granted that far apart on one chain are not a shape the SDK\n * creates.\n */\nexport async function carryLevel(\n batch: BatchPlanning,\n served: ContainerWriterProjectionResponse,\n): Promise<void> {\n const { rematerialization } = batch;\n if (carriedCount(batch) >= MAX_ROTATION_CONTAINER_REKEYS) {\n throw new Error(\n `Policy change would carry more than ${MAX_ROTATION_CONTAINER_REKEYS} descendant rekeys`,\n );\n }\n const planned = await planCarriedDescendantRekey({\n apiClient: rematerialization.apiClient,\n author: rematerialization.author,\n execSql: rematerialization.execSql,\n knownContainerKeks: batch.knownContainerKeks,\n principalPolicyCache: batch.principalPolicyCache,\n // Its path cites the policy this batch commits, so like every\n // rematerialized rotation it must cite the successor.\n replacementPrincipalPolicy: rematerialization.nextPolicy,\n resolveAuthoredPolicyReferences:\n rematerialization.resolveAuthoredPolicyReferences,\n resolveProjectionUserKey: batch.resolveProjectionUserKey,\n rotated: batch.rotatedAbove,\n served,\n stillCurrent: rematerialization.stillCurrent,\n targetSecretKey: rematerialization.targetSecretKey,\n warmReferencedPrincipalPolicies:\n rematerialization.warmReferencedPrincipalPolicies,\n });\n addPlan(batch, {\n carried: true,\n planned,\n rotated: planned.writerProjection,\n nextProjection: planned.writerProjection,\n });\n}\n\n/**\n * Re-root a granted target on the deepest path the batch authored above it, a\n * grant's included: the server applies the batch in order, so the target\n * cites every head an earlier entry advances. Its served head is pinned first,\n * as a carried rekey's is. The batch is acknowledged against the latest\n * durable pin, and a re-rooted path pins nothing, so a device that never\n * pinned this container would refuse its own batch once the server took it.\n * Only what the API served is pinned: in a compound transaction the target\n * may already sit on the preceding batch's heads, which a refused commit\n * never makes real.\n */\nexport async function rerootOnAuthoredPaths(\n batch: BatchPlanning,\n served: ContainerWriterProjectionResponse,\n): Promise<ContainerWriterProjectionResponse> {\n const rebased = rebaseOnDeepestAncestor(served, batch.authoredAbove);\n if (!rebased) return served;\n if (isSpeculativeContainerWriterProjection(served)) {\n return rebased.projection;\n }\n const { rematerialization } = batch;\n await verifyContainerWriterProjection({\n execSql: rematerialization.execSql,\n principalPolicyCache: batch.principalPolicyCache,\n projection: served,\n resolveUserKey: batch.resolveProjectionUserKey,\n stillCurrent: rematerialization.stillCurrent,\n warmReferencedPrincipalPolicies:\n rematerialization.warmReferencedPrincipalPolicies,\n });\n return rebased.projection;\n}\n\nexport function addPlan(\n batch: BatchPlanning,\n entry: PlannedRematerialization,\n): void {\n batch.plans.push(entry);\n if (entry.nextProjection) batch.authoredAbove.push(entry.nextProjection);\n if (!entry.rotated) return;\n batch.rotatedAbove.push(entry.rotated);\n const minted = entry.rotated.containerKeks.at(-1)?.containerKeyEpochId;\n if (minted) batch.knownContainerKeks.set(minted, entry.planned.containerKey);\n}\n\n/**\n * Levels of a re-rooted path pinned to an epoch the batch retires above them:\n * those strictly between the rotated ancestor the path was re-rooted on and\n * the target, parent-first, from the first stale one down. A rotation planned\n * above a target strands every level between the two, and each must be\n * re-keyed under the one above before the target can be signed. The batch\n * carries them without waiting to be told: they are proper ancestors of a\n * granted container, so the server would name them, and a refusal per level\n * is a round trip each. Anything stale above the rotated ancestor is not this\n * batch's doing and is left alone. `carryLevel` holds the count to the cap.\n */\nexport function staleLevelsAbove(\n projection: ContainerWriterProjectionResponse,\n rotatedAncestorId: string,\n): string[] {\n const keks = projection.containerKeks;\n const ancestor = keks.findIndex(\n (kek) => kek.containerId === rotatedAncestorId,\n );\n if (ancestor < 0) return [];\n const between = keks.slice(ancestor, -1);\n const firstStale = between.findIndex(\n (kek, index) =>\n index > 0 &&\n kek.parentContainerKeyEpochId !== between[index - 1]?.containerKeyEpochId,\n );\n return firstStale < 0\n ? []\n : between.slice(firstStale).map((kek) => kek.containerId);\n}\n\n/**\n * The server names a level it found pinned to a retired epoch. A batch answers\n * with a rotation of it: a carried rekey, or its own rematerialization when\n * that is a rekey or revoke. A rematerialized grant rotates nothing, so a\n * batch that both grants a container and must re-key it cannot be signed;\n * no policy change the SDK builds combines the two.\n */\nexport function assertNamedContainersRotated(\n plans: readonly PlannedRematerialization[],\n requiredContainerIds: readonly string[],\n): void {\n const rotatedIds = new Set(\n plans.flatMap((entry) =>\n entry.rotated ? [entry.planned.plan.containerId] : [],\n ),\n );\n const unrotated = requiredContainerIds\n .slice(0, MAX_ROTATION_CONTAINER_REKEYS)\n .find((containerId) => !rotatedIds.has(containerId));\n if (unrotated !== undefined) {\n throw new Error(\n `Container ${unrotated} needs a rekey this policy change only grants`,\n );\n }\n}\n\n/**\n * Plan once more after a rollback. A batch reads the served heads of what it\n * signs for, then verifies them, and this device's own re-citation of held\n * descendants, scheduled by a share above, may pin a newer head in between\n * (#2647). The second plan reads fresh heads held to the same pins, so a\n * server that serves a rolled-back head again still fails the batch.\n */\nexport async function planOnceMoreAfterRollback<T>(\n apiClient: Pick<RematerializationApi, \"evictContainerWriterProjection\">,\n containerIds: readonly string[],\n plan: () => Promise<T>,\n): Promise<T> {\n try {\n return await plan();\n } catch (error) {\n if (\n !(error instanceof KeyingVerificationError) ||\n error.code !== \"rollback\"\n )\n throw error;\n for (const containerId of containerIds) {\n apiClient.evictContainerWriterProjection?.(containerId);\n }\n return plan();\n }\n}\n"]}
|
|
@@ -76,27 +76,33 @@ async function prepareChange(change, signer, preceding) {
|
|
|
76
76
|
localPolicyCheckpoint: change.context.localPolicyCheckpoint,
|
|
77
77
|
stillCurrent: change.context.stillCurrent,
|
|
78
78
|
});
|
|
79
|
-
const
|
|
79
|
+
const authored = [
|
|
80
|
+
...(preceding?.authored ?? []),
|
|
81
|
+
{
|
|
82
|
+
organizationId: change.context.organizationDescriptor.organizationId,
|
|
83
|
+
policy: nextPolicy,
|
|
84
|
+
dependencies: [
|
|
85
|
+
change.context.organizationCurrent.policy,
|
|
86
|
+
change.context.adminCurrent.policy,
|
|
87
|
+
],
|
|
88
|
+
stillCurrent: change.context.stillCurrent,
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
// A level the batch plans under its own rotation cites this successor
|
|
92
|
+
// before the transaction commits, so the batch resolves it as authored,
|
|
93
|
+
// exactly as the next group in a compound transaction does (#2646).
|
|
94
|
+
const prepared = await change.prepareContainerMutations(nextPolicy, {
|
|
95
|
+
policies: preceding?.policies ?? [],
|
|
96
|
+
projections: preceding?.projections ?? [],
|
|
97
|
+
knownContainerKeks: preceding?.knownContainerKeks ?? new Map(),
|
|
98
|
+
authored,
|
|
99
|
+
});
|
|
80
100
|
change.request.containerMutations = [...prepared.requests];
|
|
81
101
|
return {
|
|
82
102
|
expectedHead,
|
|
83
103
|
prepared,
|
|
84
104
|
preceding: prepared.preceding
|
|
85
|
-
? {
|
|
86
|
-
...prepared.preceding,
|
|
87
|
-
authored: [
|
|
88
|
-
...(preceding?.authored ?? []),
|
|
89
|
-
{
|
|
90
|
-
organizationId: change.context.organizationDescriptor.organizationId,
|
|
91
|
-
policy: nextPolicy,
|
|
92
|
-
dependencies: [
|
|
93
|
-
change.context.organizationCurrent.policy,
|
|
94
|
-
change.context.adminCurrent.policy,
|
|
95
|
-
],
|
|
96
|
-
stillCurrent: change.context.stillCurrent,
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
}
|
|
105
|
+
? { ...prepared.preceding, authored }
|
|
100
106
|
: undefined,
|
|
101
107
|
};
|
|
102
108
|
}
|