@peerbit/trusted-network 6.0.128 → 6.0.129
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/dist/src/v2-policy-anchor.d.ts +33 -0
- package/dist/src/v2-policy-anchor.d.ts.map +1 -1
- package/dist/src/v2-policy-anchor.js +349 -72
- package/dist/src/v2-policy-anchor.js.map +1 -1
- package/dist/src/v2-policy-engine.d.ts +25 -0
- package/dist/src/v2-policy-engine.d.ts.map +1 -1
- package/dist/src/v2-policy-engine.js +187 -9
- package/dist/src/v2-policy-engine.js.map +1 -1
- package/package.json +7 -7
- package/src/v2-policy-anchor.ts +472 -81
- package/src/v2-policy-engine.ts +268 -8
package/src/v2-policy-anchor.ts
CHANGED
|
@@ -16,11 +16,14 @@ import type {
|
|
|
16
16
|
PolicyHeadProjectionV2,
|
|
17
17
|
PolicyReducerDurableStateV2,
|
|
18
18
|
PolicySnapshotResolverV2,
|
|
19
|
+
PreparedExactPolicyCandidateV2,
|
|
19
20
|
} from "./v2-policy-engine.js";
|
|
20
21
|
import {
|
|
21
22
|
TRUSTED_NETWORK_V2_MAX_PENDING_POLICIES,
|
|
22
23
|
TrustedNetworkV2PolicyReducer,
|
|
23
24
|
authenticatePolicySnapshotEntryV2,
|
|
25
|
+
captureCanonicalPolicyEntryCidV2,
|
|
26
|
+
observeAbortSignalV2,
|
|
24
27
|
} from "./v2-policy-engine.js";
|
|
25
28
|
import {
|
|
26
29
|
NetworkDescriptorV2,
|
|
@@ -59,6 +62,9 @@ export const TRUSTED_NETWORK_V2_MAX_POLICY_ANCHOR_CHECKPOINT_PAYLOAD_BYTES =
|
|
|
59
62
|
const MAX_QUEUED_POLICY_INPUT_BYTES_V2 =
|
|
60
63
|
TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES * 2;
|
|
61
64
|
const MAX_TIMER_DELAY_MS_V2 = 0x7fffffff;
|
|
65
|
+
const DEFAULT_EXACT_POLICY_HEAD_TIMEOUT_MS_V2 = 10 * 1000;
|
|
66
|
+
const DEFAULT_EXACT_POLICY_HEAD_MAX_ANCESTRY_STEPS_V2 =
|
|
67
|
+
TRUSTED_NETWORK_V2_MAX_PENDING_POLICIES;
|
|
62
68
|
const ZERO_DIGEST = new Uint8Array(32);
|
|
63
69
|
const textEncoder = new TextEncoder();
|
|
64
70
|
const CHECKPOINT_SCOPE_DOMAIN = textEncoder.encode(
|
|
@@ -84,11 +90,18 @@ const isAbortSignalV2 = (value: unknown): value is AbortSignal => {
|
|
|
84
90
|
}
|
|
85
91
|
};
|
|
86
92
|
|
|
93
|
+
const boundedDependencyReasonV2 = (error: unknown): string =>
|
|
94
|
+
(error instanceof Error ? error.message : String(error)).slice(
|
|
95
|
+
0,
|
|
96
|
+
MAX_UNAVAILABLE_REASON_LENGTH,
|
|
97
|
+
);
|
|
98
|
+
|
|
87
99
|
export type CrashSafePolicyAnchorStoreV2 = CrashSafeAtomicReplaceStore;
|
|
88
100
|
|
|
89
101
|
type DurableReducerOptionsV2 = {
|
|
90
102
|
descriptor: NetworkDescriptorV2;
|
|
91
103
|
resolvePolicyEntry: PolicySnapshotResolverV2;
|
|
104
|
+
resolvePolicyEntryByCid?: PolicyEntryCidResolverV2;
|
|
92
105
|
resolveTimeoutMs?: number;
|
|
93
106
|
signal?: AbortSignal;
|
|
94
107
|
maxPending?: number;
|
|
@@ -100,6 +113,25 @@ export type TrustedNetworkV2DurablePolicyReducerOptions =
|
|
|
100
113
|
store: CrashSafePolicyAnchorStoreV2;
|
|
101
114
|
};
|
|
102
115
|
|
|
116
|
+
export type PolicyEntryCidResolverV2 = (
|
|
117
|
+
policyEntryCid: string,
|
|
118
|
+
options: { signal: AbortSignal },
|
|
119
|
+
) => Uint8Array | undefined | Promise<Uint8Array | undefined>;
|
|
120
|
+
|
|
121
|
+
export type ExactPolicyHeadRequirementV2 = {
|
|
122
|
+
policyEntryCid: string;
|
|
123
|
+
maxAncestrySteps?: number;
|
|
124
|
+
/** Queue-inclusive relative deadline; callers may only shorten the 10s cap. */
|
|
125
|
+
timeoutMs?: number;
|
|
126
|
+
deadline?: number;
|
|
127
|
+
signal?: AbortSignal;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export type ExactPolicyHeadLeaseV2 = {
|
|
131
|
+
policyEntryCid: string;
|
|
132
|
+
policy: PolicyHeadProjectionV2;
|
|
133
|
+
};
|
|
134
|
+
|
|
103
135
|
export type PolicyLeaseReferenceV2 = {
|
|
104
136
|
sequence: bigint;
|
|
105
137
|
digest: Uint8Array;
|
|
@@ -127,6 +159,15 @@ export type PolicyLeaseResultV2<T> =
|
|
|
127
159
|
reason: string;
|
|
128
160
|
};
|
|
129
161
|
|
|
162
|
+
type PolicyLeaseFailureV2 = Exclude<
|
|
163
|
+
PolicyLeaseResultV2<never>,
|
|
164
|
+
{ status: "completed" }
|
|
165
|
+
>;
|
|
166
|
+
|
|
167
|
+
type PolicyLeaseAcquisitionV2<T> =
|
|
168
|
+
| { status: "resolved"; lease: T }
|
|
169
|
+
| PolicyLeaseFailureV2;
|
|
170
|
+
|
|
130
171
|
/**
|
|
131
172
|
* One self-contained application snapshot. The enclosing generic checkpoint
|
|
132
173
|
* supplies generation, scope, predecessor, and checksum authentication.
|
|
@@ -740,6 +781,11 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
740
781
|
private checkpoint?: CrashSafeTwoSlotCheckpoint;
|
|
741
782
|
private core: TrustedNetworkV2PolicyReducer;
|
|
742
783
|
private published: PublishedProjectionV2;
|
|
784
|
+
private readonly descriptor: NetworkDescriptorV2;
|
|
785
|
+
private readonly resolvePolicyEntryByCid?: PolicyEntryCidResolverV2;
|
|
786
|
+
private readonly leaseLifecycleController: AbortController;
|
|
787
|
+
private readonly disposeLifecycleObservation: () => void;
|
|
788
|
+
private exactAuthenticationInFlight?: Promise<PreparedExactPolicyCandidateV2>;
|
|
743
789
|
private durableCoreIdentityBytes?: Uint8Array;
|
|
744
790
|
private operationTail: Promise<void> = Promise.resolve();
|
|
745
791
|
private authorizationFences = 0;
|
|
@@ -750,11 +796,18 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
750
796
|
private constructor(properties: {
|
|
751
797
|
checkpoint?: CrashSafeTwoSlotCheckpoint;
|
|
752
798
|
core: TrustedNetworkV2PolicyReducer;
|
|
799
|
+
descriptor: NetworkDescriptorV2;
|
|
800
|
+
resolvePolicyEntryByCid?: PolicyEntryCidResolverV2;
|
|
801
|
+
lifecycle: ReturnType<typeof observeAbortSignalV2>;
|
|
753
802
|
durableCoreIdentityBytes?: Uint8Array;
|
|
754
803
|
}) {
|
|
755
804
|
this.checkpoint = properties.checkpoint;
|
|
756
805
|
this.core = properties.core;
|
|
757
806
|
this.published = publishedFromReducer(this.core);
|
|
807
|
+
this.descriptor = properties.descriptor;
|
|
808
|
+
this.resolvePolicyEntryByCid = properties.resolvePolicyEntryByCid;
|
|
809
|
+
this.leaseLifecycleController = properties.lifecycle[0];
|
|
810
|
+
this.disposeLifecycleObservation = properties.lifecycle[1];
|
|
758
811
|
this.durableCoreIdentityBytes =
|
|
759
812
|
properties.durableCoreIdentityBytes === undefined
|
|
760
813
|
? undefined
|
|
@@ -765,11 +818,18 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
765
818
|
options: TrustedNetworkV2DurablePolicyReducerOptions,
|
|
766
819
|
): Promise<TrustedNetworkV2DurablePolicyReducer> {
|
|
767
820
|
assertNetworkDescriptorV2(options.descriptor);
|
|
821
|
+
if (
|
|
822
|
+
options.resolvePolicyEntryByCid !== undefined &&
|
|
823
|
+
typeof options.resolvePolicyEntryByCid !== "function"
|
|
824
|
+
) {
|
|
825
|
+
throw new TypeError("Policy CID resolver must be a function");
|
|
826
|
+
}
|
|
768
827
|
const descriptor = deserialize(
|
|
769
828
|
serialize(options.descriptor),
|
|
770
829
|
NetworkDescriptorV2,
|
|
771
830
|
);
|
|
772
|
-
const
|
|
831
|
+
const lifecycle = observeAbortSignalV2(options.signal);
|
|
832
|
+
const signal = lifecycle[0].signal;
|
|
773
833
|
const store = options.store;
|
|
774
834
|
const coreProperties: DurableReducerOptionsV2 = {
|
|
775
835
|
descriptor,
|
|
@@ -779,39 +839,41 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
779
839
|
maxPending: options.maxPending,
|
|
780
840
|
maxPendingPolicyBytes: options.maxPendingPolicyBytes,
|
|
781
841
|
};
|
|
782
|
-
assertOpenNotAborted(signal);
|
|
783
|
-
const checkpoint = await CrashSafeTwoSlotCheckpoint.open({
|
|
784
|
-
store,
|
|
785
|
-
scope: checkpointScope(descriptor),
|
|
786
|
-
maxPayloadBytes:
|
|
787
|
-
TRUSTED_NETWORK_V2_MAX_POLICY_ANCHOR_CHECKPOINT_PAYLOAD_BYTES,
|
|
788
|
-
});
|
|
789
|
-
assertOpenNotAborted(signal);
|
|
790
|
-
|
|
791
|
-
// Never let the old append-only format look like an empty or newer
|
|
792
|
-
// checkpoint. It was internal and never activated, so migration or fallback
|
|
793
|
-
// would add rollback surface without preserving a public compatibility need.
|
|
794
|
-
for await (const [key] of store.iterator()) {
|
|
795
|
-
assertOpenNotAborted(signal);
|
|
796
|
-
if (
|
|
797
|
-
key === TRUSTED_NETWORK_V2_POLICY_ANCHOR_STORE_OWNER ||
|
|
798
|
-
key.startsWith(`${TRUSTED_NETWORK_V2_POLICY_ANCHOR_STORE_OWNER}/`)
|
|
799
|
-
) {
|
|
800
|
-
throw new Error(
|
|
801
|
-
"Legacy append-only policy-anchor records are not supported; reset the dedicated store",
|
|
802
|
-
);
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
assertOpenNotAborted(signal);
|
|
806
|
-
|
|
807
|
-
const current = checkpoint.current;
|
|
808
842
|
let core: TrustedNetworkV2PolicyReducer | undefined;
|
|
809
843
|
try {
|
|
844
|
+
assertOpenNotAborted(signal);
|
|
845
|
+
const checkpoint = await CrashSafeTwoSlotCheckpoint.open({
|
|
846
|
+
store,
|
|
847
|
+
scope: checkpointScope(descriptor),
|
|
848
|
+
maxPayloadBytes:
|
|
849
|
+
TRUSTED_NETWORK_V2_MAX_POLICY_ANCHOR_CHECKPOINT_PAYLOAD_BYTES,
|
|
850
|
+
});
|
|
851
|
+
assertOpenNotAborted(signal);
|
|
852
|
+
|
|
853
|
+
// Never let the old append-only format look like an empty or newer
|
|
854
|
+
// checkpoint. It was internal and never activated, so migration or fallback
|
|
855
|
+
// would add rollback surface without preserving a public compatibility need.
|
|
856
|
+
for await (const [key] of store.iterator()) {
|
|
857
|
+
assertOpenNotAborted(signal);
|
|
858
|
+
if (
|
|
859
|
+
key === TRUSTED_NETWORK_V2_POLICY_ANCHOR_STORE_OWNER ||
|
|
860
|
+
key.startsWith(`${TRUSTED_NETWORK_V2_POLICY_ANCHOR_STORE_OWNER}/`)
|
|
861
|
+
) {
|
|
862
|
+
throw new Error(
|
|
863
|
+
"Legacy append-only policy-anchor records are not supported; reset the dedicated store",
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
assertOpenNotAborted(signal);
|
|
868
|
+
const current = checkpoint.current;
|
|
810
869
|
if (current === undefined) {
|
|
811
870
|
core = new TrustedNetworkV2PolicyReducer(coreProperties);
|
|
812
871
|
return new TrustedNetworkV2DurablePolicyReducer({
|
|
813
872
|
checkpoint,
|
|
814
873
|
core,
|
|
874
|
+
descriptor,
|
|
875
|
+
resolvePolicyEntryByCid: options.resolvePolicyEntryByCid,
|
|
876
|
+
lifecycle,
|
|
815
877
|
});
|
|
816
878
|
}
|
|
817
879
|
|
|
@@ -834,10 +896,14 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
834
896
|
// storage, so release that potentially 8.78 MiB snapshot immediately.
|
|
835
897
|
checkpoint: forked ? undefined : checkpoint,
|
|
836
898
|
core,
|
|
899
|
+
descriptor,
|
|
900
|
+
resolvePolicyEntryByCid: options.resolvePolicyEntryByCid,
|
|
901
|
+
lifecycle,
|
|
837
902
|
durableCoreIdentityBytes: decoded.coreIdentityBytes,
|
|
838
903
|
});
|
|
839
904
|
} catch (error) {
|
|
840
905
|
core?.abort();
|
|
906
|
+
lifecycle[1]();
|
|
841
907
|
throw error;
|
|
842
908
|
}
|
|
843
909
|
}
|
|
@@ -916,6 +982,7 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
916
982
|
|
|
917
983
|
abort(): void {
|
|
918
984
|
this.core.abort();
|
|
985
|
+
this.disposeLifecycleObservation();
|
|
919
986
|
}
|
|
920
987
|
|
|
921
988
|
ingest(entryBytes: Uint8Array): Promise<PolicyAdmissionResultV2> {
|
|
@@ -1110,38 +1177,242 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
1110
1177
|
});
|
|
1111
1178
|
}
|
|
1112
1179
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1180
|
+
let digest: Uint8Array;
|
|
1181
|
+
try {
|
|
1182
|
+
digest = copyBytes(suppliedDigest);
|
|
1183
|
+
} catch {
|
|
1184
|
+
return Promise.resolve({
|
|
1185
|
+
status: "rejected",
|
|
1186
|
+
reason: "Policy reference digest must contain exactly 32 bytes",
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
return this.withSerializedPolicyLease(
|
|
1191
|
+
32,
|
|
1192
|
+
deadline,
|
|
1193
|
+
signal,
|
|
1194
|
+
async (leaseSignal) => {
|
|
1195
|
+
const resolution = await this.core.resolveAcceptedPolicyPrefix(
|
|
1196
|
+
{ sequence, digest },
|
|
1197
|
+
{ maxSteps, deadline, signal: leaseSignal },
|
|
1198
|
+
);
|
|
1199
|
+
return resolution.status === "resolved"
|
|
1200
|
+
? {
|
|
1201
|
+
status: "resolved",
|
|
1202
|
+
lease: {
|
|
1203
|
+
policy: resolution.policy,
|
|
1204
|
+
acceptedHead: resolution.acceptedHead,
|
|
1205
|
+
},
|
|
1206
|
+
}
|
|
1207
|
+
: resolution;
|
|
1208
|
+
},
|
|
1209
|
+
use,
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* Run one callback while a CID-addressed policy wrapper is authenticated and
|
|
1215
|
+
* its policy-body identity is the stable durable current head.
|
|
1216
|
+
*
|
|
1217
|
+
* Exact fetch, prepared admission, crash-safe publication, and callback share
|
|
1218
|
+
* one bounded budget and serialized queue slot. No digest-resolver fallback is
|
|
1219
|
+
* allowed when the CID resolver is absent or unavailable.
|
|
1220
|
+
*/
|
|
1221
|
+
withExactPolicyHead<T>(
|
|
1222
|
+
requirement: ExactPolicyHeadRequirementV2,
|
|
1223
|
+
use: (lease: ExactPolicyHeadLeaseV2) => T | Promise<T>,
|
|
1224
|
+
): Promise<PolicyLeaseResultV2<T>> {
|
|
1225
|
+
const halted = this.immediateLeaseHaltedResult();
|
|
1226
|
+
if (halted !== undefined) return Promise.resolve(halted);
|
|
1227
|
+
|
|
1228
|
+
let policyEntryCid: string;
|
|
1229
|
+
let maxAncestrySteps: number;
|
|
1230
|
+
let deadline: number;
|
|
1231
|
+
let signal: AbortSignal | undefined;
|
|
1232
|
+
try {
|
|
1233
|
+
policyEntryCid = captureCanonicalPolicyEntryCidV2(
|
|
1234
|
+
requirement.policyEntryCid,
|
|
1235
|
+
);
|
|
1236
|
+
maxAncestrySteps =
|
|
1237
|
+
requirement.maxAncestrySteps ??
|
|
1238
|
+
DEFAULT_EXACT_POLICY_HEAD_MAX_ANCESTRY_STEPS_V2;
|
|
1239
|
+
if (
|
|
1240
|
+
!Number.isSafeInteger(maxAncestrySteps) ||
|
|
1241
|
+
maxAncestrySteps < 0 ||
|
|
1242
|
+
maxAncestrySteps > TRUSTED_NETWORK_V2_MAX_PENDING_POLICIES
|
|
1243
|
+
) {
|
|
1244
|
+
return Promise.resolve({
|
|
1245
|
+
status: "rejected",
|
|
1246
|
+
reason: `Exact policy maxAncestrySteps must be between 0 and ${TRUSTED_NETWORK_V2_MAX_PENDING_POLICIES}`,
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
const suppliedDeadline = requirement.deadline;
|
|
1250
|
+
if (
|
|
1251
|
+
suppliedDeadline !== undefined &&
|
|
1252
|
+
(!Number.isSafeInteger(suppliedDeadline) || suppliedDeadline < 0)
|
|
1253
|
+
) {
|
|
1254
|
+
return Promise.resolve({
|
|
1255
|
+
status: "rejected",
|
|
1256
|
+
reason: "Exact policy deadline must be a non-negative safe integer",
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
const timeoutMs =
|
|
1260
|
+
requirement.timeoutMs ?? DEFAULT_EXACT_POLICY_HEAD_TIMEOUT_MS_V2;
|
|
1261
|
+
if (
|
|
1262
|
+
!Number.isSafeInteger(timeoutMs) ||
|
|
1263
|
+
timeoutMs < 0 ||
|
|
1264
|
+
timeoutMs > DEFAULT_EXACT_POLICY_HEAD_TIMEOUT_MS_V2
|
|
1265
|
+
) {
|
|
1266
|
+
return Promise.resolve({
|
|
1267
|
+
status: "rejected",
|
|
1268
|
+
reason: `Exact policy timeoutMs must be between 0 and ${DEFAULT_EXACT_POLICY_HEAD_TIMEOUT_MS_V2}`,
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
const relativeDeadline = Math.min(
|
|
1272
|
+
Number.MAX_SAFE_INTEGER,
|
|
1273
|
+
Date.now() + timeoutMs,
|
|
1274
|
+
);
|
|
1275
|
+
deadline =
|
|
1276
|
+
suppliedDeadline === undefined
|
|
1277
|
+
? relativeDeadline
|
|
1278
|
+
: Math.min(suppliedDeadline, relativeDeadline);
|
|
1279
|
+
signal = requirement.signal;
|
|
1280
|
+
if (signal !== undefined && !isAbortSignalV2(signal)) {
|
|
1281
|
+
return Promise.resolve({
|
|
1282
|
+
status: "rejected",
|
|
1283
|
+
reason: "Exact policy signal must be an AbortSignal",
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
} catch {
|
|
1287
|
+
return Promise.resolve({
|
|
1288
|
+
status: "rejected",
|
|
1289
|
+
reason: "Exact policy-head requirement is invalid",
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
if (typeof use !== "function") {
|
|
1293
|
+
return Promise.resolve({
|
|
1294
|
+
status: "rejected",
|
|
1295
|
+
reason: "Exact policy-head callback must be a function",
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
return this.withSerializedPolicyLease(
|
|
1300
|
+
textEncoder.encode(policyEntryCid).byteLength,
|
|
1301
|
+
deadline,
|
|
1302
|
+
signal,
|
|
1303
|
+
async (leaseSignal, mutationCompleted) => {
|
|
1304
|
+
const exact = await this.resolveExactPolicyEntry(
|
|
1305
|
+
policyEntryCid,
|
|
1306
|
+
deadline,
|
|
1307
|
+
leaseSignal,
|
|
1308
|
+
);
|
|
1309
|
+
if (exact.status !== "resolved") return exact;
|
|
1310
|
+
this.authorizationFences += 1;
|
|
1311
|
+
try {
|
|
1312
|
+
const admission = await this.core.ingestPreparedExactPolicy(
|
|
1313
|
+
exact.lease,
|
|
1314
|
+
{
|
|
1315
|
+
maxParentEdges: maxAncestrySteps,
|
|
1316
|
+
deadline,
|
|
1317
|
+
signal: leaseSignal,
|
|
1318
|
+
},
|
|
1319
|
+
);
|
|
1320
|
+
// Core admission may now be mutated. From here through checkpoint
|
|
1321
|
+
// settlement, cancellation may settle outward but cannot release the slot.
|
|
1322
|
+
mutationCompleted();
|
|
1323
|
+
await this.persistCorePublication(admission);
|
|
1324
|
+
const halted = this.immediateLeaseHaltedResult();
|
|
1325
|
+
if (halted !== undefined) return halted;
|
|
1326
|
+
if (
|
|
1327
|
+
admission.status !== "accepted" &&
|
|
1328
|
+
admission.status !== "duplicate"
|
|
1329
|
+
) {
|
|
1330
|
+
return {
|
|
1331
|
+
status:
|
|
1332
|
+
admission.status === "rejected" ||
|
|
1333
|
+
admission.status === "capacity"
|
|
1334
|
+
? admission.status
|
|
1335
|
+
: "unavailable",
|
|
1336
|
+
reason:
|
|
1337
|
+
admission.reason ?? "Exact policy admission is unavailable",
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
const head = this.published.head;
|
|
1341
|
+
if (
|
|
1342
|
+
this.published.state !== "ACTIVE" ||
|
|
1343
|
+
head === undefined ||
|
|
1344
|
+
head.sequence !== exact.lease.policy.sequence ||
|
|
1345
|
+
!equals(head.digest, exact.lease.policy.digest)
|
|
1346
|
+
) {
|
|
1347
|
+
return {
|
|
1348
|
+
status: "unavailable",
|
|
1349
|
+
reason:
|
|
1350
|
+
"Exact policy entry is not the durable current policy head",
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
return {
|
|
1354
|
+
status: "resolved",
|
|
1355
|
+
lease: { policyEntryCid, policy: copyHead(head)! },
|
|
1356
|
+
};
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
throw this.halt(error);
|
|
1359
|
+
} finally {
|
|
1360
|
+
this.authorizationFences -= 1;
|
|
1361
|
+
}
|
|
1362
|
+
},
|
|
1363
|
+
use,
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
private withSerializedPolicyLease<T, Lease>(
|
|
1368
|
+
retainedInputBytes: number,
|
|
1369
|
+
deadline: number | undefined,
|
|
1370
|
+
signal: AbortSignal | undefined,
|
|
1371
|
+
acquire: (
|
|
1372
|
+
signal: AbortSignal,
|
|
1373
|
+
mutationCompleted: () => void,
|
|
1374
|
+
) => Promise<PolicyLeaseAcquisitionV2<Lease>>,
|
|
1375
|
+
use: (lease: Lease) => T | Promise<T>,
|
|
1376
|
+
): Promise<PolicyLeaseResultV2<T>> {
|
|
1377
|
+
if (!this.reserveOperation(retainedInputBytes)) {
|
|
1115
1378
|
return Promise.resolve({
|
|
1116
1379
|
status: "capacity",
|
|
1117
1380
|
reason: "Durable policy operation queue is at its fixed capacity",
|
|
1118
1381
|
});
|
|
1119
1382
|
}
|
|
1120
|
-
let
|
|
1383
|
+
let observedSignal: ReturnType<typeof observeAbortSignalV2>;
|
|
1121
1384
|
try {
|
|
1122
|
-
|
|
1385
|
+
observedSignal = observeAbortSignalV2(signal);
|
|
1123
1386
|
} catch {
|
|
1124
|
-
this.releaseOperation(
|
|
1387
|
+
this.releaseOperation(retainedInputBytes);
|
|
1125
1388
|
return Promise.resolve({
|
|
1126
1389
|
status: "rejected",
|
|
1127
|
-
reason: "Policy
|
|
1390
|
+
reason: "Policy lease signal could not be observed",
|
|
1128
1391
|
});
|
|
1129
1392
|
}
|
|
1130
1393
|
|
|
1131
1394
|
let queueAcquired = false;
|
|
1132
1395
|
let callbackAcquired = false;
|
|
1396
|
+
let mutationCompleted = false;
|
|
1397
|
+
const acquisitionController = observedSignal[0];
|
|
1398
|
+
const interruptionSignal = AbortSignal.any([
|
|
1399
|
+
acquisitionController.signal,
|
|
1400
|
+
this.leaseLifecycleController.signal,
|
|
1401
|
+
]);
|
|
1133
1402
|
let preAcquisitionResult: PolicyLeaseResultV2<T> | undefined;
|
|
1134
1403
|
let resolveEarly!: (result: PolicyLeaseResultV2<T>) => void;
|
|
1135
1404
|
const earlyResult = new Promise<PolicyLeaseResultV2<T>>((resolve) => {
|
|
1136
1405
|
resolveEarly = resolve;
|
|
1137
1406
|
});
|
|
1138
1407
|
let deadlineTimer: ReturnType<typeof setTimeout> | undefined;
|
|
1139
|
-
let signalListenerInstalled = false;
|
|
1140
1408
|
|
|
1141
|
-
const
|
|
1409
|
+
const interruptBeforeAcquisition = (): void => {
|
|
1410
|
+
const halted = this.leaseLifecycleController.signal.aborted;
|
|
1142
1411
|
settleBeforeAcquisition({
|
|
1143
|
-
status: "unavailable",
|
|
1144
|
-
reason:
|
|
1412
|
+
status: halted ? "halted" : "unavailable",
|
|
1413
|
+
reason: halted
|
|
1414
|
+
? "Policy reducer lifecycle is aborted"
|
|
1415
|
+
: "Policy lease acquisition was aborted by the caller",
|
|
1145
1416
|
});
|
|
1146
1417
|
};
|
|
1147
1418
|
const cleanupWakeups = (): void => {
|
|
@@ -1149,31 +1420,29 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
1149
1420
|
clearTimeout(deadlineTimer);
|
|
1150
1421
|
deadlineTimer = undefined;
|
|
1151
1422
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
// A structurally valid hostile signal must not break queue cleanup.
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1423
|
+
interruptionSignal.removeEventListener(
|
|
1424
|
+
"abort",
|
|
1425
|
+
interruptBeforeAcquisition,
|
|
1426
|
+
);
|
|
1427
|
+
observedSignal[1]();
|
|
1160
1428
|
};
|
|
1161
1429
|
function settleBeforeAcquisition(result: PolicyLeaseResultV2<T>): void {
|
|
1162
|
-
if (
|
|
1163
|
-
queueAcquired ||
|
|
1164
|
-
callbackAcquired ||
|
|
1165
|
-
preAcquisitionResult !== undefined
|
|
1166
|
-
) {
|
|
1430
|
+
if (callbackAcquired || preAcquisitionResult !== undefined) {
|
|
1167
1431
|
return;
|
|
1168
1432
|
}
|
|
1169
1433
|
preAcquisitionResult = result;
|
|
1434
|
+
acquisitionController.abort();
|
|
1170
1435
|
cleanupWakeups();
|
|
1171
|
-
resolveEarly(result);
|
|
1436
|
+
if (!queueAcquired || mutationCompleted) resolveEarly(result);
|
|
1172
1437
|
}
|
|
1438
|
+
const completeMutation = (): void => {
|
|
1439
|
+
mutationCompleted = true;
|
|
1440
|
+
if (preAcquisitionResult !== undefined)
|
|
1441
|
+
resolveEarly(preAcquisitionResult);
|
|
1442
|
+
};
|
|
1173
1443
|
const armDeadlineWakeup = (): void => {
|
|
1174
1444
|
if (
|
|
1175
1445
|
deadline === undefined ||
|
|
1176
|
-
queueAcquired ||
|
|
1177
1446
|
callbackAcquired ||
|
|
1178
1447
|
preAcquisitionResult !== undefined
|
|
1179
1448
|
) {
|
|
@@ -1209,26 +1478,22 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
1209
1478
|
return preAcquisitionResult;
|
|
1210
1479
|
}
|
|
1211
1480
|
queueAcquired = true;
|
|
1212
|
-
cleanupWakeups();
|
|
1213
1481
|
const queuedHalt = this.immediateLeaseHaltedResult();
|
|
1214
1482
|
if (queuedHalt !== undefined) return queuedHalt;
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
digest,
|
|
1219
|
-
},
|
|
1220
|
-
{ maxSteps, deadline, signal },
|
|
1483
|
+
const acquisition = await acquire(
|
|
1484
|
+
acquisitionController.signal,
|
|
1485
|
+
completeMutation,
|
|
1221
1486
|
);
|
|
1222
1487
|
if (preAcquisitionResult !== undefined) {
|
|
1223
1488
|
return preAcquisitionResult;
|
|
1224
1489
|
}
|
|
1225
|
-
if (
|
|
1490
|
+
if (acquisition.status !== "resolved") return acquisition;
|
|
1226
1491
|
// Lifecycle abort before callback invocation loses acquisition. There is
|
|
1227
1492
|
// no await between this check and invoking user code, which is the lease's
|
|
1228
1493
|
// linearization point.
|
|
1229
1494
|
const resolvedHalt = this.immediateLeaseHaltedResult();
|
|
1230
1495
|
if (resolvedHalt !== undefined) return resolvedHalt;
|
|
1231
|
-
if (signal
|
|
1496
|
+
if (acquisitionController.signal.aborted) {
|
|
1232
1497
|
return {
|
|
1233
1498
|
status: "unavailable",
|
|
1234
1499
|
reason: "Policy lease acquisition was aborted by the caller",
|
|
@@ -1242,39 +1507,161 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
1242
1507
|
}
|
|
1243
1508
|
callbackAcquired = true;
|
|
1244
1509
|
cleanupWakeups();
|
|
1245
|
-
const value = await use(
|
|
1246
|
-
// The core created these two projections independently and retains
|
|
1247
|
-
// neither, including when the requested policy is the current head.
|
|
1248
|
-
policy: resolution.policy,
|
|
1249
|
-
acceptedHead: resolution.acceptedHead,
|
|
1250
|
-
});
|
|
1510
|
+
const value = await use(acquisition.lease);
|
|
1251
1511
|
return { status: "completed", value };
|
|
1252
1512
|
});
|
|
1253
1513
|
const retainedResult = queuedResult.finally(() => {
|
|
1254
1514
|
cleanupWakeups();
|
|
1255
|
-
this.releaseOperation(
|
|
1515
|
+
this.releaseOperation(retainedInputBytes);
|
|
1256
1516
|
});
|
|
1257
1517
|
this.operationTail = retainedResult.then(
|
|
1258
1518
|
(): void => {},
|
|
1259
1519
|
(): void => {},
|
|
1260
1520
|
);
|
|
1261
1521
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1522
|
+
interruptionSignal.addEventListener("abort", interruptBeforeAcquisition, {
|
|
1523
|
+
once: true,
|
|
1524
|
+
});
|
|
1525
|
+
if (interruptionSignal.aborted) interruptBeforeAcquisition();
|
|
1526
|
+
armDeadlineWakeup();
|
|
1527
|
+
return Promise.race([retainedResult, earlyResult]);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
private async resolveExactPolicyEntry(
|
|
1531
|
+
policyEntryCid: string,
|
|
1532
|
+
deadline: number,
|
|
1533
|
+
callerSignal: AbortSignal,
|
|
1534
|
+
): Promise<PolicyLeaseAcquisitionV2<PreparedExactPolicyCandidateV2>> {
|
|
1535
|
+
const resolver = this.resolvePolicyEntryByCid;
|
|
1536
|
+
if (resolver === undefined) {
|
|
1537
|
+
return {
|
|
1538
|
+
status: "unavailable",
|
|
1539
|
+
reason: "Exact policy-head readiness has no policy CID resolver",
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
if (this.core.state === "HALTED") {
|
|
1543
|
+
return {
|
|
1544
|
+
status: "halted",
|
|
1545
|
+
reason: "Policy reducer lifecycle is aborted",
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
if (callerSignal.aborted) {
|
|
1549
|
+
return {
|
|
1550
|
+
status: "unavailable",
|
|
1551
|
+
reason: "Exact policy-head acquisition was aborted by the caller",
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
if (Date.now() >= deadline) {
|
|
1555
|
+
return {
|
|
1556
|
+
status: "unavailable",
|
|
1557
|
+
reason: "Exact policy-head acquisition deadline elapsed",
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
const interrupted = new Promise<never>((_resolve, reject) => {
|
|
1562
|
+
callerSignal.addEventListener(
|
|
1563
|
+
"abort",
|
|
1564
|
+
() => reject(new Error("Exact policy-head resolution interrupted")),
|
|
1565
|
+
{ once: true },
|
|
1566
|
+
);
|
|
1567
|
+
});
|
|
1568
|
+
const interruptionFailure = (): PolicyLeaseFailureV2 | undefined => {
|
|
1569
|
+
const halted = this.immediateLeaseHaltedResult();
|
|
1570
|
+
if (halted !== undefined) return halted;
|
|
1571
|
+
if (callerSignal.aborted) {
|
|
1572
|
+
return {
|
|
1573
|
+
status: "unavailable",
|
|
1574
|
+
reason: "Exact policy-head acquisition was aborted by the caller",
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
if (Date.now() >= deadline) {
|
|
1578
|
+
return {
|
|
1579
|
+
status: "unavailable",
|
|
1580
|
+
reason: "Exact policy-head acquisition deadline elapsed",
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
return undefined;
|
|
1584
|
+
};
|
|
1585
|
+
|
|
1586
|
+
const priorAuthentication = this.exactAuthenticationInFlight;
|
|
1587
|
+
if (priorAuthentication !== undefined) {
|
|
1264
1588
|
try {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1589
|
+
await Promise.race([
|
|
1590
|
+
priorAuthentication.catch((): void => {}),
|
|
1591
|
+
interrupted,
|
|
1592
|
+
]);
|
|
1268
1593
|
} catch {
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1594
|
+
return (
|
|
1595
|
+
interruptionFailure() ?? {
|
|
1596
|
+
status: "unavailable",
|
|
1597
|
+
reason: "Exact policy authentication slot is unavailable",
|
|
1598
|
+
}
|
|
1599
|
+
);
|
|
1273
1600
|
}
|
|
1274
|
-
|
|
1601
|
+
const interruptedResult = interruptionFailure();
|
|
1602
|
+
if (interruptedResult !== undefined) return interruptedResult;
|
|
1275
1603
|
}
|
|
1276
|
-
|
|
1277
|
-
|
|
1604
|
+
let entryBytes: Uint8Array | undefined;
|
|
1605
|
+
try {
|
|
1606
|
+
entryBytes = await Promise.race([
|
|
1607
|
+
Promise.resolve().then(() =>
|
|
1608
|
+
resolver(policyEntryCid, { signal: callerSignal }),
|
|
1609
|
+
),
|
|
1610
|
+
interrupted,
|
|
1611
|
+
]);
|
|
1612
|
+
} catch (error) {
|
|
1613
|
+
const interruptedResult = interruptionFailure();
|
|
1614
|
+
if (interruptedResult !== undefined) return interruptedResult;
|
|
1615
|
+
return {
|
|
1616
|
+
status: "unavailable",
|
|
1617
|
+
reason: `Policy CID resolver dependency is unavailable: ${boundedDependencyReasonV2(error)}`,
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
if (entryBytes === undefined) {
|
|
1621
|
+
return {
|
|
1622
|
+
status: "unavailable",
|
|
1623
|
+
reason: "Exact policy entry is unavailable from its CID resolver",
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
const authentication = Promise.resolve().then(() =>
|
|
1627
|
+
this.authenticateExactPolicyEntry({
|
|
1628
|
+
policyEntryCid,
|
|
1629
|
+
entryBytes,
|
|
1630
|
+
descriptor: this.descriptor,
|
|
1631
|
+
}),
|
|
1632
|
+
);
|
|
1633
|
+
this.exactAuthenticationInFlight = authentication;
|
|
1634
|
+
const clearAuthentication = (): void => {
|
|
1635
|
+
if (this.exactAuthenticationInFlight === authentication) {
|
|
1636
|
+
this.exactAuthenticationInFlight = undefined;
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
void authentication.then(clearAuthentication, clearAuthentication);
|
|
1640
|
+
try {
|
|
1641
|
+
const authenticated = await Promise.race([authentication, interrupted]);
|
|
1642
|
+
const completedInterruption = interruptionFailure();
|
|
1643
|
+
return (
|
|
1644
|
+
completedInterruption ?? { status: "resolved", lease: authenticated }
|
|
1645
|
+
);
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
const interruptedResult = interruptionFailure();
|
|
1648
|
+
if (interruptedResult !== undefined) return interruptedResult;
|
|
1649
|
+
return {
|
|
1650
|
+
status: "rejected",
|
|
1651
|
+
reason: `Exact policy entry is invalid: ${boundedDependencyReasonV2(error)}`,
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
private authenticateExactPolicyEntry(properties: {
|
|
1657
|
+
policyEntryCid: string;
|
|
1658
|
+
entryBytes: Uint8Array;
|
|
1659
|
+
descriptor: NetworkDescriptorV2;
|
|
1660
|
+
}): Promise<PreparedExactPolicyCandidateV2> {
|
|
1661
|
+
return this.core.prepareExactPolicyEntry(
|
|
1662
|
+
properties.policyEntryCid,
|
|
1663
|
+
properties.entryBytes,
|
|
1664
|
+
);
|
|
1278
1665
|
}
|
|
1279
1666
|
|
|
1280
1667
|
private forkFailStopResult(): PolicyAdmissionResultV2 {
|
|
@@ -1297,7 +1684,7 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
1297
1684
|
};
|
|
1298
1685
|
}
|
|
1299
1686
|
|
|
1300
|
-
private immediateLeaseHaltedResult():
|
|
1687
|
+
private immediateLeaseHaltedResult(): PolicyLeaseFailureV2 | undefined {
|
|
1301
1688
|
if (this.terminalError !== undefined) {
|
|
1302
1689
|
return {
|
|
1303
1690
|
status: "halted",
|
|
@@ -1440,6 +1827,10 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
1440
1827
|
throw new Error("Policy-anchor checkpoint is unavailable");
|
|
1441
1828
|
}
|
|
1442
1829
|
await checkpoint.commit(payloadBytes);
|
|
1830
|
+
// Atomic replacement is not cancellable. A lifecycle that ended while it
|
|
1831
|
+
// was in flight may leave durable recovery state, but cannot publish it into
|
|
1832
|
+
// the stale in-memory instance.
|
|
1833
|
+
if (this.core.state === "HALTED") return;
|
|
1443
1834
|
|
|
1444
1835
|
this.durableCoreIdentityBytes = nextDurableCoreIdentityBytes;
|
|
1445
1836
|
this.published = nextPublished;
|