@tangle-network/agent-provider-tangle 0.14.0 → 0.14.2
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/README.md +61 -54
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/tangle-confidential-attestation.d.ts +31 -0
- package/dist/tangle-confidential-attestation.js +165 -0
- package/dist/tangle-interactive.d.ts +2 -2
- package/dist/tangle-interactive.js +95 -140
- package/dist/tangle-types.d.ts +36 -45
- package/dist/tangle-workspace-branching.js +294 -73
- package/package.json +11 -9
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ConfidentialAttestationSchema, ConfidentialExecutionRequestSchema, ForkedEnvironmentRefSchema, WorkspaceCheckpointRefSchema, WorkspaceCheckpointRequestSchema, WorkspaceCleanupAcknowledgementSchema, WorkspaceCleanupRequestSchema, WorkspaceForkRequestSchema, WorkspaceOperationLookupRequestSchema, WorkspaceCheckpointResultSchema, WorkspaceCheckpointLookupResultSchema, WorkspaceForkResultSchema, WorkspaceForkLookupResultSchema, canonicalCandidateDigest, confidentialExecutionVerified, sha256Bytes, } from "@tangle-network/agent-interface";
|
|
2
2
|
import { awaitWithSignal, boundedIdentifier, boundedString, assertBoundedJson, MAX_LIST_RESULTS, MAX_STRING_LENGTH, SANDBOX_LIST_PAGE_SIZE, } from "./tangle-contract-safety.js";
|
|
3
|
+
import { encodeTangleConfidentialAttestationQuote, MAX_TEE_EVIDENCE_BYTES, MAX_TEE_MEASUREMENT_BYTES, } from "./tangle-confidential-attestation.js";
|
|
3
4
|
/**
|
|
4
5
|
* Namespace used for provider recovery metadata.
|
|
5
6
|
*
|
|
@@ -20,7 +21,9 @@ const MAX_MARKER_CHUNKS = 512;
|
|
|
20
21
|
* accepts it is exact-optional, so an explicit `undefined` is not an absent key.
|
|
21
22
|
*/
|
|
22
23
|
export function confidentialVerifierOption(verifier) {
|
|
23
|
-
return verifier === undefined
|
|
24
|
+
return verifier === undefined
|
|
25
|
+
? {}
|
|
26
|
+
: { confidentialAttestationVerifier: verifier };
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* Build the exact workspace contract over the managed Sandbox operations.
|
|
@@ -50,20 +53,32 @@ export function createTangleWorkspaceBranching(options) {
|
|
|
50
53
|
if (local) {
|
|
51
54
|
return local.request.requestDigest === request.requestDigest
|
|
52
55
|
? { state: "known", record: local }
|
|
53
|
-
: {
|
|
56
|
+
: {
|
|
57
|
+
state: "conflict",
|
|
58
|
+
existingRequestDigest: local.request.requestDigest,
|
|
59
|
+
};
|
|
54
60
|
}
|
|
55
61
|
const recovered = await findCheckpointByKey(box, request.idempotencyKey, signal);
|
|
56
62
|
if (recovered === undefined) {
|
|
57
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
state: "undecided",
|
|
65
|
+
message: "Sandbox checkpoint inventory is unavailable",
|
|
66
|
+
};
|
|
58
67
|
}
|
|
59
68
|
if (!recovered)
|
|
60
69
|
return { state: "absent" };
|
|
61
70
|
if (recovered.marker.requestDigest !== request.requestDigest) {
|
|
62
|
-
return {
|
|
71
|
+
return {
|
|
72
|
+
state: "conflict",
|
|
73
|
+
existingRequestDigest: recovered.marker.requestDigest,
|
|
74
|
+
};
|
|
63
75
|
}
|
|
64
76
|
const record = checkpointRecordFromSnapshot(recovered.marker.request, recovered.snapshot);
|
|
65
77
|
if (!record) {
|
|
66
|
-
return {
|
|
78
|
+
return {
|
|
79
|
+
state: "undecided",
|
|
80
|
+
message: "Sandbox checkpoint metadata is invalid",
|
|
81
|
+
};
|
|
67
82
|
}
|
|
68
83
|
checkpoints.set(request.idempotencyKey, record);
|
|
69
84
|
return { state: "known", record };
|
|
@@ -74,26 +89,45 @@ export function createTangleWorkspaceBranching(options) {
|
|
|
74
89
|
if (local) {
|
|
75
90
|
return local.request.requestDigest === request.requestDigest
|
|
76
91
|
? { state: "known", record: local }
|
|
77
|
-
: {
|
|
92
|
+
: {
|
|
93
|
+
state: "conflict",
|
|
94
|
+
existingRequestDigest: local.request.requestDigest,
|
|
95
|
+
};
|
|
78
96
|
}
|
|
79
97
|
const recovered = await findForkByKey(client, box, provider, request.idempotencyKey, signal);
|
|
80
98
|
if (recovered === undefined) {
|
|
81
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
state: "undecided",
|
|
101
|
+
message: "Sandbox child inventory is unavailable",
|
|
102
|
+
};
|
|
82
103
|
}
|
|
83
104
|
if (!recovered)
|
|
84
105
|
return { state: "absent" };
|
|
85
106
|
if (recovered.marker.requestDigest !== request.requestDigest) {
|
|
86
|
-
return {
|
|
107
|
+
return {
|
|
108
|
+
state: "conflict",
|
|
109
|
+
existingRequestDigest: recovered.marker.requestDigest,
|
|
110
|
+
};
|
|
87
111
|
}
|
|
88
112
|
const child = await completeForkChild(client, recovered.child, signal);
|
|
89
113
|
if (!child) {
|
|
90
|
-
return {
|
|
114
|
+
return {
|
|
115
|
+
state: "undecided",
|
|
116
|
+
message: "Sandbox fork child identity is incomplete",
|
|
117
|
+
};
|
|
91
118
|
}
|
|
92
119
|
const environment = await environmentFromChild(recovered.marker.request, child, provider, child.createdAt, options.confidentialAttestationVerifier, signal);
|
|
93
120
|
if (!environment) {
|
|
94
|
-
return {
|
|
95
|
-
|
|
96
|
-
|
|
121
|
+
return {
|
|
122
|
+
state: "undecided",
|
|
123
|
+
message: "Sandbox fork child identity is incomplete",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const record = {
|
|
127
|
+
request: recovered.marker.request,
|
|
128
|
+
environment,
|
|
129
|
+
child,
|
|
130
|
+
};
|
|
97
131
|
forks.set(request.idempotencyKey, record);
|
|
98
132
|
return { state: "known", record };
|
|
99
133
|
};
|
|
@@ -296,14 +330,34 @@ export function createTangleWorkspaceBranching(options) {
|
|
|
296
330
|
const returnedChild = result.children[0];
|
|
297
331
|
const child = await completeForkChild(client, returnedChild, operation?.signal);
|
|
298
332
|
if (!child) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return
|
|
333
|
+
const compensation = forkMarkerFromMetadata(returnedChild.metadata) === undefined
|
|
334
|
+
? await compensateUnmarkedForkChild(result, returnedChild, operation?.signal)
|
|
335
|
+
: "not_attempted";
|
|
336
|
+
const removed = compensation === "destroyed" || compensation === "already_absent";
|
|
337
|
+
return forkUnknown(request, removed
|
|
338
|
+
? "Sandbox fork returned a child without a complete identity; the newly created child was removed"
|
|
339
|
+
: compensation === "unconfirmed"
|
|
340
|
+
? "Sandbox fork returned a child without a complete identity; child cleanup was not confirmed"
|
|
341
|
+
: "Sandbox fork returned a child without a complete identity", !removed);
|
|
342
|
+
}
|
|
343
|
+
const childMarker = forkMarkerFromMetadata(child.metadata);
|
|
344
|
+
if (childMarker) {
|
|
345
|
+
if (!markerBelongsToSource(childMarker, provider, box.id)) {
|
|
346
|
+
return forkUnknown(request, "Sandbox fork returned a child marked for another source", true);
|
|
347
|
+
}
|
|
348
|
+
if (childMarker.idempotencyKey !== request.idempotencyKey ||
|
|
349
|
+
childMarker.requestDigest !== request.requestDigest) {
|
|
350
|
+
return forkConflict(request, childMarker.requestDigest);
|
|
351
|
+
}
|
|
304
352
|
}
|
|
305
353
|
if (!childMarker) {
|
|
306
|
-
|
|
354
|
+
const compensation = await compensateUnmarkedForkChild(result, child, operation?.signal);
|
|
355
|
+
const removed = compensation === "destroyed" || compensation === "already_absent";
|
|
356
|
+
return forkUnknown(request, removed
|
|
357
|
+
? "Sandbox fork acknowledgement omitted its provider recovery marker; the newly created child was removed"
|
|
358
|
+
: compensation === "unconfirmed"
|
|
359
|
+
? "Sandbox fork acknowledgement omitted its provider recovery marker; child cleanup was not confirmed"
|
|
360
|
+
: "Sandbox fork acknowledgement omitted its provider recovery marker", !removed);
|
|
307
361
|
}
|
|
308
362
|
const environment = await environmentFromChild(request, child, provider, child.createdAt, options.confidentialAttestationVerifier, operation?.signal);
|
|
309
363
|
if (!environment) {
|
|
@@ -468,7 +522,8 @@ function checkpointRecordFromSnapshot(request, snapshot) {
|
|
|
468
522
|
async function environmentFromChild(request, child, provider, createdAt, verifier, signal) {
|
|
469
523
|
signal?.throwIfAborted();
|
|
470
524
|
const environmentId = safeIdentifier(child.id);
|
|
471
|
-
if (!environmentId ||
|
|
525
|
+
if (!environmentId ||
|
|
526
|
+
environmentId === request.checkpoint.source.environmentId) {
|
|
472
527
|
return undefined;
|
|
473
528
|
}
|
|
474
529
|
if (createdAt === undefined)
|
|
@@ -504,7 +559,9 @@ async function environmentFromChild(request, child, provider, createdAt, verifie
|
|
|
504
559
|
createdAt: normalizedCreatedAt,
|
|
505
560
|
placement: request.placement,
|
|
506
561
|
confidentialRequested: request.confidential?.requested === true,
|
|
507
|
-
...(attestation === undefined
|
|
562
|
+
...(attestation === undefined
|
|
563
|
+
? {}
|
|
564
|
+
: { confidentialAttestation: attestation }),
|
|
508
565
|
...(attestation === undefined ? {} : { confidential: true }),
|
|
509
566
|
...(metadata === undefined ? {} : { metadata }),
|
|
510
567
|
});
|
|
@@ -513,7 +570,8 @@ async function environmentFromChild(request, child, provider, createdAt, verifie
|
|
|
513
570
|
}
|
|
514
571
|
async function confidentialAttestationForChild(request, child, provider, verifier, signal) {
|
|
515
572
|
const confidential = ConfidentialExecutionRequestSchema.parse(request.confidential);
|
|
516
|
-
if (!confidential.requested ||
|
|
573
|
+
if (!confidential.requested ||
|
|
574
|
+
typeof child.getTeeAttestation !== "function") {
|
|
517
575
|
return undefined;
|
|
518
576
|
}
|
|
519
577
|
let response;
|
|
@@ -532,7 +590,7 @@ async function confidentialAttestationForChild(request, child, provider, verifie
|
|
|
532
590
|
return undefined;
|
|
533
591
|
}
|
|
534
592
|
const measurement = sha256Bytes(Uint8Array.from(response.attestation.measurement));
|
|
535
|
-
const quote =
|
|
593
|
+
const quote = encodeTangleConfidentialAttestationQuote(response.attestation);
|
|
536
594
|
if (quote === undefined)
|
|
537
595
|
return undefined;
|
|
538
596
|
let verifiedAt;
|
|
@@ -586,7 +644,8 @@ async function confidentialAttestationForChild(request, child, provider, verifie
|
|
|
586
644
|
!safeIdentifier(verification.providerKeyId) ||
|
|
587
645
|
!safeString(verification.providerSignature) ||
|
|
588
646
|
verification.providerSignature === quote ||
|
|
589
|
-
(verification.measurement !== undefined &&
|
|
647
|
+
(verification.measurement !== undefined &&
|
|
648
|
+
verification.measurement !== measurement)) {
|
|
590
649
|
return undefined;
|
|
591
650
|
}
|
|
592
651
|
const attestation = ConfidentialAttestationSchema.safeParse({
|
|
@@ -607,23 +666,23 @@ async function confidentialAttestationForChild(request, child, provider, verifie
|
|
|
607
666
|
: undefined;
|
|
608
667
|
}
|
|
609
668
|
function validTeeReport(report) {
|
|
610
|
-
return !!report &&
|
|
669
|
+
return (!!report &&
|
|
611
670
|
safeString(report.tee_type) !== undefined &&
|
|
612
671
|
Array.isArray(report.evidence) &&
|
|
613
|
-
report.evidence.length <=
|
|
672
|
+
report.evidence.length <= MAX_TEE_EVIDENCE_BYTES &&
|
|
614
673
|
report.evidence.every((value) => Number.isInteger(value) && value >= 0 && value <= 255) &&
|
|
615
674
|
Array.isArray(report.measurement) &&
|
|
616
|
-
report.measurement.length <=
|
|
675
|
+
report.measurement.length <= MAX_TEE_MEASUREMENT_BYTES &&
|
|
617
676
|
report.measurement.every((value) => Number.isInteger(value) && value >= 0 && value <= 255) &&
|
|
618
677
|
Number.isFinite(report.timestamp) &&
|
|
619
|
-
report.timestamp > 0;
|
|
678
|
+
report.timestamp > 0);
|
|
620
679
|
}
|
|
621
680
|
function validSnapshotResult(result) {
|
|
622
|
-
return !!result &&
|
|
681
|
+
return (!!result &&
|
|
623
682
|
safeIdentifier(result.snapshotId) !== undefined &&
|
|
624
683
|
validDate(result.createdAt) &&
|
|
625
684
|
Array.isArray(result.tags) &&
|
|
626
|
-
result.tags.every((tag) => safeString(tag) !== undefined);
|
|
685
|
+
result.tags.every((tag) => safeString(tag) !== undefined));
|
|
627
686
|
}
|
|
628
687
|
function validSnapshotInfo(snapshot, sandboxId) {
|
|
629
688
|
return (validSnapshotResult(snapshot) &&
|
|
@@ -631,14 +690,14 @@ function validSnapshotInfo(snapshot, sandboxId) {
|
|
|
631
690
|
(sandboxId === undefined || snapshot.sandboxId === sandboxId));
|
|
632
691
|
}
|
|
633
692
|
function validForkResult(result) {
|
|
634
|
-
return !!result &&
|
|
693
|
+
return (!!result &&
|
|
635
694
|
Array.isArray(result.children) &&
|
|
636
695
|
result.children.every((child) => child !== null &&
|
|
637
696
|
typeof child === "object" &&
|
|
638
697
|
safeIdentifier(child.id) !== undefined) &&
|
|
639
698
|
result.requestedCount === 1 &&
|
|
640
699
|
result.materializedCount === result.children.length &&
|
|
641
|
-
typeof result.complete === "boolean";
|
|
700
|
+
typeof result.complete === "boolean");
|
|
642
701
|
}
|
|
643
702
|
function checkpointMarkerTags(request) {
|
|
644
703
|
const marker = {
|
|
@@ -802,7 +861,9 @@ async function findManagedCheckpoint(box, provider, id, expected, signal) {
|
|
|
802
861
|
canonicalCandidateDigest(expected.source))) {
|
|
803
862
|
return false;
|
|
804
863
|
}
|
|
805
|
-
return (await checkpointOperationSucceeded(box, marker, signal))
|
|
864
|
+
return (await checkpointOperationSucceeded(box, marker, signal))
|
|
865
|
+
? true
|
|
866
|
+
: "unknown";
|
|
806
867
|
}
|
|
807
868
|
catch {
|
|
808
869
|
signal?.throwIfAborted();
|
|
@@ -839,7 +900,9 @@ async function findForkChildById(client, box, provider, id, signal) {
|
|
|
839
900
|
const marker = forkMarkerFromMetadata(child.metadata);
|
|
840
901
|
if (!marker || !markerBelongsToSource(marker, provider, box.id))
|
|
841
902
|
return undefined;
|
|
842
|
-
return (await forkOperationSucceeded(box, marker, signal))
|
|
903
|
+
return (await forkOperationSucceeded(box, marker, signal))
|
|
904
|
+
? child
|
|
905
|
+
: undefined;
|
|
843
906
|
}
|
|
844
907
|
catch {
|
|
845
908
|
signal?.throwIfAborted();
|
|
@@ -847,21 +910,26 @@ async function findForkChildById(client, box, provider, id, signal) {
|
|
|
847
910
|
}
|
|
848
911
|
}
|
|
849
912
|
/**
|
|
850
|
-
* Resolve a complete child identity when an
|
|
913
|
+
* Resolve a complete child identity when an acknowledgement omits durable data.
|
|
851
914
|
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
915
|
+
* A branch response can precede a richer registry read during a rolling
|
|
916
|
+
* deployment. Recover the exact child when its creation time or provider
|
|
917
|
+
* marker is absent. Never invent either field from the request.
|
|
855
918
|
*/
|
|
856
919
|
async function completeForkChild(client, child, signal) {
|
|
857
|
-
if (child.createdAt !== undefined
|
|
920
|
+
if (child.createdAt !== undefined &&
|
|
921
|
+
forkMarkerFromMetadata(child.metadata) !== undefined) {
|
|
858
922
|
return child;
|
|
859
|
-
|
|
923
|
+
}
|
|
924
|
+
if (typeof client.get !== "function" ||
|
|
925
|
+
safeIdentifier(child.id) === undefined) {
|
|
860
926
|
return undefined;
|
|
861
927
|
}
|
|
862
928
|
try {
|
|
863
929
|
const resolved = await awaitWithSignal(client.get(child.id, signal ? { signal } : undefined), signal);
|
|
864
|
-
if (!resolved ||
|
|
930
|
+
if (!resolved ||
|
|
931
|
+
resolved.id !== child.id ||
|
|
932
|
+
resolved.createdAt === undefined) {
|
|
865
933
|
return undefined;
|
|
866
934
|
}
|
|
867
935
|
return resolved;
|
|
@@ -871,6 +939,26 @@ async function completeForkChild(client, child, signal) {
|
|
|
871
939
|
return undefined;
|
|
872
940
|
}
|
|
873
941
|
}
|
|
942
|
+
/** Remove only a child this exact call confirmed it created. */
|
|
943
|
+
async function compensateUnmarkedForkChild(result, child, signal) {
|
|
944
|
+
if (result.idempotency?.outcome !== "created")
|
|
945
|
+
return "not_attempted";
|
|
946
|
+
if (typeof child.delete !== "function")
|
|
947
|
+
return "unconfirmed";
|
|
948
|
+
try {
|
|
949
|
+
const acknowledgement = (await awaitWithSignal(child.delete(), signal));
|
|
950
|
+
if (acknowledgement?.sandboxId !== child.id ||
|
|
951
|
+
(acknowledgement.outcome !== "destroyed" &&
|
|
952
|
+
acknowledgement.outcome !== "already_absent")) {
|
|
953
|
+
return "unconfirmed";
|
|
954
|
+
}
|
|
955
|
+
return acknowledgement.outcome;
|
|
956
|
+
}
|
|
957
|
+
catch {
|
|
958
|
+
signal?.throwIfAborted();
|
|
959
|
+
return "unconfirmed";
|
|
960
|
+
}
|
|
961
|
+
}
|
|
874
962
|
async function findBlockingForks(box, client, provider, checkpointId, signal) {
|
|
875
963
|
const candidates = await listMarkedForkChildren(client, box, provider, undefined, signal);
|
|
876
964
|
if (candidates === undefined)
|
|
@@ -958,7 +1046,9 @@ async function listMarkedForkChildren(client, box, provider, key, signal) {
|
|
|
958
1046
|
return undefined;
|
|
959
1047
|
const marked = [];
|
|
960
1048
|
for (const child of children) {
|
|
961
|
-
if (!child ||
|
|
1049
|
+
if (!child ||
|
|
1050
|
+
typeof child !== "object" ||
|
|
1051
|
+
safeIdentifier(child.id) === undefined) {
|
|
962
1052
|
return undefined;
|
|
963
1053
|
}
|
|
964
1054
|
if (child.id === box.id)
|
|
@@ -997,13 +1087,16 @@ function currentCheckpointMarkerFromTags(tags, key, base) {
|
|
|
997
1087
|
const keyTag = tags.find((tag) => tag.startsWith(`${base}-key-`));
|
|
998
1088
|
if (keyTag &&
|
|
999
1089
|
key !== undefined &&
|
|
1000
|
-
keyTag.slice(`${base}-key-`.length) !==
|
|
1090
|
+
keyTag.slice(`${base}-key-`.length) !==
|
|
1091
|
+
markerKeyDigest(key).replace(":", "-")) {
|
|
1001
1092
|
return undefined;
|
|
1002
1093
|
}
|
|
1003
1094
|
const chunks = tags
|
|
1004
1095
|
.map((tag) => {
|
|
1005
1096
|
const match = tag.match(new RegExp(`^${escapeRegExp(base)}-material-(\\d+)-(\\d+)-([A-Za-z0-9_-]+)$`));
|
|
1006
|
-
return match
|
|
1097
|
+
return match
|
|
1098
|
+
? { index: Number(match[1]), total: Number(match[2]), chunk: match[3] }
|
|
1099
|
+
: undefined;
|
|
1007
1100
|
})
|
|
1008
1101
|
.filter((value) => value !== undefined)
|
|
1009
1102
|
.sort((left, right) => left.index - right.index);
|
|
@@ -1053,12 +1146,17 @@ function checkpointMarkerFromUnknown(value, key, legacy = false) {
|
|
|
1053
1146
|
if (!value || typeof value !== "object")
|
|
1054
1147
|
return undefined;
|
|
1055
1148
|
const parsed = value;
|
|
1056
|
-
if (parsed.version !== 1 ||
|
|
1149
|
+
if (parsed.version !== 1 ||
|
|
1150
|
+
parsed.kind !== "checkpoint" ||
|
|
1151
|
+
typeof parsed.idempotencyKey !== "string" ||
|
|
1152
|
+
typeof parsed.requestDigest !== "string")
|
|
1057
1153
|
return undefined;
|
|
1058
1154
|
if (key !== undefined && parsed.idempotencyKey !== key)
|
|
1059
1155
|
return undefined;
|
|
1060
1156
|
const request = WorkspaceCheckpointRequestSchema.safeParse(parsed.request);
|
|
1061
|
-
if (!request.success ||
|
|
1157
|
+
if (!request.success ||
|
|
1158
|
+
request.data.idempotencyKey !== parsed.idempotencyKey ||
|
|
1159
|
+
request.data.requestDigest !== parsed.requestDigest)
|
|
1062
1160
|
return undefined;
|
|
1063
1161
|
return {
|
|
1064
1162
|
version: 1,
|
|
@@ -1079,14 +1177,25 @@ function forkMarkerFromMetadata(metadata, key) {
|
|
|
1079
1177
|
if (!value || typeof value !== "object")
|
|
1080
1178
|
return undefined;
|
|
1081
1179
|
const parsed = value;
|
|
1082
|
-
if (parsed.version !== 1 ||
|
|
1180
|
+
if (parsed.version !== 1 ||
|
|
1181
|
+
parsed.kind !== "fork" ||
|
|
1182
|
+
typeof parsed.idempotencyKey !== "string" ||
|
|
1183
|
+
typeof parsed.requestDigest !== "string")
|
|
1083
1184
|
return undefined;
|
|
1084
1185
|
if (key !== undefined && parsed.idempotencyKey !== key)
|
|
1085
1186
|
return undefined;
|
|
1086
1187
|
const request = WorkspaceForkRequestSchema.safeParse(parsed.request);
|
|
1087
|
-
if (!request.success ||
|
|
1188
|
+
if (!request.success ||
|
|
1189
|
+
request.data.idempotencyKey !== parsed.idempotencyKey ||
|
|
1190
|
+
request.data.requestDigest !== parsed.requestDigest)
|
|
1088
1191
|
return undefined;
|
|
1089
|
-
return {
|
|
1192
|
+
return {
|
|
1193
|
+
version: 1,
|
|
1194
|
+
kind: "fork",
|
|
1195
|
+
idempotencyKey: parsed.idempotencyKey,
|
|
1196
|
+
requestDigest: parsed.requestDigest,
|
|
1197
|
+
request: request.data,
|
|
1198
|
+
};
|
|
1090
1199
|
}
|
|
1091
1200
|
/**
|
|
1092
1201
|
* Turn a failed create into a conflict only from provider-owned material.
|
|
@@ -1134,7 +1243,11 @@ async function forkConflictFromRemote(client, box, provider, request, verifier,
|
|
|
1134
1243
|
*/
|
|
1135
1244
|
function lookupOutcomeFromSandbox(lookup, kind) {
|
|
1136
1245
|
if (!lookup || lookup.kind !== kind) {
|
|
1137
|
-
return {
|
|
1246
|
+
return {
|
|
1247
|
+
absent: false,
|
|
1248
|
+
message: `Sandbox returned no ${kind} lookup`,
|
|
1249
|
+
retryable: true,
|
|
1250
|
+
};
|
|
1138
1251
|
}
|
|
1139
1252
|
if (lookup.outcome === "conflict") {
|
|
1140
1253
|
return {
|
|
@@ -1143,70 +1256,173 @@ function lookupOutcomeFromSandbox(lookup, kind) {
|
|
|
1143
1256
|
retryable: false,
|
|
1144
1257
|
};
|
|
1145
1258
|
}
|
|
1146
|
-
if (lookup.outcome !== "not_found" &&
|
|
1147
|
-
|
|
1259
|
+
if (lookup.outcome !== "not_found" &&
|
|
1260
|
+
(lookup.outcome === "unknown" || lookup.state !== "succeeded")) {
|
|
1261
|
+
return {
|
|
1262
|
+
absent: false,
|
|
1263
|
+
message: `Sandbox ${kind} operation is not decided`,
|
|
1264
|
+
retryable: true,
|
|
1265
|
+
};
|
|
1148
1266
|
}
|
|
1149
1267
|
return { absent: true };
|
|
1150
1268
|
}
|
|
1151
1269
|
function checkpointSuccess(request, checkpoint, status) {
|
|
1152
|
-
return WorkspaceCheckpointResultSchema.parse({
|
|
1270
|
+
return WorkspaceCheckpointResultSchema.parse({
|
|
1271
|
+
status,
|
|
1272
|
+
idempotencyKey: request.idempotencyKey,
|
|
1273
|
+
requestDigest: request.requestDigest,
|
|
1274
|
+
checkpoint,
|
|
1275
|
+
});
|
|
1153
1276
|
}
|
|
1154
1277
|
function checkpointConflict(request, existingRequestDigest) {
|
|
1155
|
-
return WorkspaceCheckpointResultSchema.parse({
|
|
1278
|
+
return WorkspaceCheckpointResultSchema.parse({
|
|
1279
|
+
status: "conflict",
|
|
1280
|
+
idempotencyKey: request.idempotencyKey,
|
|
1281
|
+
requestDigest: request.requestDigest,
|
|
1282
|
+
existingRequestDigest,
|
|
1283
|
+
});
|
|
1156
1284
|
}
|
|
1157
1285
|
function checkpointUnknown(request, message, retryable) {
|
|
1158
|
-
return WorkspaceCheckpointResultSchema.parse({
|
|
1286
|
+
return WorkspaceCheckpointResultSchema.parse({
|
|
1287
|
+
status: "unknown",
|
|
1288
|
+
idempotencyKey: request.idempotencyKey,
|
|
1289
|
+
requestDigest: request.requestDigest,
|
|
1290
|
+
message: boundedString(message, "Tangle checkpoint error"),
|
|
1291
|
+
retryable,
|
|
1292
|
+
});
|
|
1159
1293
|
}
|
|
1160
1294
|
function checkpointFound(request, checkpoint) {
|
|
1161
|
-
return WorkspaceCheckpointLookupResultSchema.parse({
|
|
1295
|
+
return WorkspaceCheckpointLookupResultSchema.parse({
|
|
1296
|
+
status: "found",
|
|
1297
|
+
idempotencyKey: request.idempotencyKey,
|
|
1298
|
+
requestDigest: request.requestDigest,
|
|
1299
|
+
checkpoint,
|
|
1300
|
+
});
|
|
1162
1301
|
}
|
|
1163
1302
|
function checkpointNotFound(request) {
|
|
1164
|
-
return WorkspaceCheckpointLookupResultSchema.parse({
|
|
1303
|
+
return WorkspaceCheckpointLookupResultSchema.parse({
|
|
1304
|
+
status: "not_found",
|
|
1305
|
+
idempotencyKey: request.idempotencyKey,
|
|
1306
|
+
requestDigest: request.requestDigest,
|
|
1307
|
+
});
|
|
1165
1308
|
}
|
|
1166
1309
|
function checkpointLookupConflict(request, existingRequestDigest) {
|
|
1167
|
-
return WorkspaceCheckpointLookupResultSchema.parse({
|
|
1310
|
+
return WorkspaceCheckpointLookupResultSchema.parse({
|
|
1311
|
+
status: "conflict",
|
|
1312
|
+
idempotencyKey: request.idempotencyKey,
|
|
1313
|
+
requestDigest: request.requestDigest,
|
|
1314
|
+
existingRequestDigest,
|
|
1315
|
+
});
|
|
1168
1316
|
}
|
|
1169
1317
|
function checkpointLookupUnknown(request, message, retryable) {
|
|
1170
|
-
return WorkspaceCheckpointLookupResultSchema.parse({
|
|
1318
|
+
return WorkspaceCheckpointLookupResultSchema.parse({
|
|
1319
|
+
status: "unknown",
|
|
1320
|
+
idempotencyKey: request.idempotencyKey,
|
|
1321
|
+
requestDigest: request.requestDigest,
|
|
1322
|
+
message: boundedString(message, "Tangle checkpoint lookup error"),
|
|
1323
|
+
retryable,
|
|
1324
|
+
});
|
|
1171
1325
|
}
|
|
1172
1326
|
function forkSuccess(request, environment, status) {
|
|
1173
|
-
return WorkspaceForkResultSchema.parse({
|
|
1327
|
+
return WorkspaceForkResultSchema.parse({
|
|
1328
|
+
status,
|
|
1329
|
+
idempotencyKey: request.idempotencyKey,
|
|
1330
|
+
requestDigest: request.requestDigest,
|
|
1331
|
+
environment,
|
|
1332
|
+
});
|
|
1174
1333
|
}
|
|
1175
1334
|
function forkConflict(request, existingRequestDigest) {
|
|
1176
|
-
return WorkspaceForkResultSchema.parse({
|
|
1335
|
+
return WorkspaceForkResultSchema.parse({
|
|
1336
|
+
status: "conflict",
|
|
1337
|
+
idempotencyKey: request.idempotencyKey,
|
|
1338
|
+
requestDigest: request.requestDigest,
|
|
1339
|
+
existingRequestDigest,
|
|
1340
|
+
});
|
|
1177
1341
|
}
|
|
1178
1342
|
function forkUnknown(request, message, retryable) {
|
|
1179
|
-
return WorkspaceForkResultSchema.parse({
|
|
1343
|
+
return WorkspaceForkResultSchema.parse({
|
|
1344
|
+
status: "unknown",
|
|
1345
|
+
idempotencyKey: request.idempotencyKey,
|
|
1346
|
+
requestDigest: request.requestDigest,
|
|
1347
|
+
message: boundedString(message, "Tangle fork error"),
|
|
1348
|
+
retryable,
|
|
1349
|
+
});
|
|
1180
1350
|
}
|
|
1181
1351
|
function forkFound(request, environment) {
|
|
1182
|
-
return WorkspaceForkLookupResultSchema.parse({
|
|
1352
|
+
return WorkspaceForkLookupResultSchema.parse({
|
|
1353
|
+
status: "found",
|
|
1354
|
+
idempotencyKey: request.idempotencyKey,
|
|
1355
|
+
requestDigest: request.requestDigest,
|
|
1356
|
+
environment,
|
|
1357
|
+
});
|
|
1183
1358
|
}
|
|
1184
1359
|
function forkNotFound(request) {
|
|
1185
|
-
return WorkspaceForkLookupResultSchema.parse({
|
|
1360
|
+
return WorkspaceForkLookupResultSchema.parse({
|
|
1361
|
+
status: "not_found",
|
|
1362
|
+
idempotencyKey: request.idempotencyKey,
|
|
1363
|
+
requestDigest: request.requestDigest,
|
|
1364
|
+
});
|
|
1186
1365
|
}
|
|
1187
1366
|
function forkLookupConflict(request, existingRequestDigest) {
|
|
1188
|
-
return WorkspaceForkLookupResultSchema.parse({
|
|
1367
|
+
return WorkspaceForkLookupResultSchema.parse({
|
|
1368
|
+
status: "conflict",
|
|
1369
|
+
idempotencyKey: request.idempotencyKey,
|
|
1370
|
+
requestDigest: request.requestDigest,
|
|
1371
|
+
existingRequestDigest,
|
|
1372
|
+
});
|
|
1189
1373
|
}
|
|
1190
1374
|
function forkLookupUnknown(request, message, retryable) {
|
|
1191
|
-
return WorkspaceForkLookupResultSchema.parse({
|
|
1375
|
+
return WorkspaceForkLookupResultSchema.parse({
|
|
1376
|
+
status: "unknown",
|
|
1377
|
+
idempotencyKey: request.idempotencyKey,
|
|
1378
|
+
requestDigest: request.requestDigest,
|
|
1379
|
+
message: boundedString(message, "Tangle fork lookup error"),
|
|
1380
|
+
retryable,
|
|
1381
|
+
});
|
|
1192
1382
|
}
|
|
1193
1383
|
function cleanupDeleted(request) {
|
|
1194
|
-
return WorkspaceCleanupAcknowledgementSchema.parse({
|
|
1384
|
+
return WorkspaceCleanupAcknowledgementSchema.parse({
|
|
1385
|
+
...request,
|
|
1386
|
+
status: "deleted",
|
|
1387
|
+
});
|
|
1195
1388
|
}
|
|
1196
1389
|
function cleanupAlreadyAbsent(request) {
|
|
1197
|
-
return WorkspaceCleanupAcknowledgementSchema.parse({
|
|
1390
|
+
return WorkspaceCleanupAcknowledgementSchema.parse({
|
|
1391
|
+
...request,
|
|
1392
|
+
status: "already_absent",
|
|
1393
|
+
});
|
|
1198
1394
|
}
|
|
1199
1395
|
function cleanupConflict(request, existingRequestDigest) {
|
|
1200
|
-
return WorkspaceCleanupAcknowledgementSchema.parse({
|
|
1396
|
+
return WorkspaceCleanupAcknowledgementSchema.parse({
|
|
1397
|
+
...request,
|
|
1398
|
+
status: "conflict",
|
|
1399
|
+
existingRequestDigest,
|
|
1400
|
+
message: "Cleanup operation id is bound to another target",
|
|
1401
|
+
});
|
|
1201
1402
|
}
|
|
1202
1403
|
function cleanupInUse(request, blockingTargetIds) {
|
|
1203
|
-
return WorkspaceCleanupAcknowledgementSchema.parse({
|
|
1404
|
+
return WorkspaceCleanupAcknowledgementSchema.parse({
|
|
1405
|
+
...request,
|
|
1406
|
+
status: "in_use",
|
|
1407
|
+
blockingTargetIds,
|
|
1408
|
+
message: "Checkpoint is still referenced by forked environments",
|
|
1409
|
+
});
|
|
1204
1410
|
}
|
|
1205
1411
|
function cleanupUnknown(request, message, retryable) {
|
|
1206
|
-
return WorkspaceCleanupAcknowledgementSchema.parse({
|
|
1412
|
+
return WorkspaceCleanupAcknowledgementSchema.parse({
|
|
1413
|
+
...request,
|
|
1414
|
+
status: "unknown",
|
|
1415
|
+
message: boundedString(message, "Tangle cleanup error"),
|
|
1416
|
+
retryable,
|
|
1417
|
+
});
|
|
1207
1418
|
}
|
|
1208
1419
|
function cleanupTransportFailure(request, message, retryable) {
|
|
1209
|
-
return WorkspaceCleanupAcknowledgementSchema.parse({
|
|
1420
|
+
return WorkspaceCleanupAcknowledgementSchema.parse({
|
|
1421
|
+
...request,
|
|
1422
|
+
status: "transport_failure",
|
|
1423
|
+
message: boundedString(message, "Tangle cleanup transport error"),
|
|
1424
|
+
retryable,
|
|
1425
|
+
});
|
|
1210
1426
|
}
|
|
1211
1427
|
function isoDate(value) {
|
|
1212
1428
|
const date = value instanceof Date ? value : new Date(value);
|
|
@@ -1225,12 +1441,17 @@ function cloneJson(value) {
|
|
|
1225
1441
|
return structuredClone(value);
|
|
1226
1442
|
}
|
|
1227
1443
|
function safeIdentifier(value) {
|
|
1228
|
-
if (typeof value !== "string" ||
|
|
1444
|
+
if (typeof value !== "string" ||
|
|
1445
|
+
value.length === 0 ||
|
|
1446
|
+
value.length > 512 ||
|
|
1447
|
+
value.trim() !== value)
|
|
1229
1448
|
return undefined;
|
|
1230
1449
|
return value;
|
|
1231
1450
|
}
|
|
1232
1451
|
function safeString(value) {
|
|
1233
|
-
if (typeof value !== "string" ||
|
|
1452
|
+
if (typeof value !== "string" ||
|
|
1453
|
+
value.length === 0 ||
|
|
1454
|
+
value.length > MAX_STRING_LENGTH)
|
|
1234
1455
|
return undefined;
|
|
1235
1456
|
return value;
|
|
1236
1457
|
}
|