@peerbit/trusted-network 6.0.128 → 6.0.130
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
|
@@ -18,6 +18,7 @@ export type CrashSafePolicyAnchorStoreV2 = CrashSafeAtomicReplaceStore;
|
|
|
18
18
|
type DurableReducerOptionsV2 = {
|
|
19
19
|
descriptor: NetworkDescriptorV2;
|
|
20
20
|
resolvePolicyEntry: PolicySnapshotResolverV2;
|
|
21
|
+
resolvePolicyEntryByCid?: PolicyEntryCidResolverV2;
|
|
21
22
|
resolveTimeoutMs?: number;
|
|
22
23
|
signal?: AbortSignal;
|
|
23
24
|
maxPending?: number;
|
|
@@ -26,6 +27,21 @@ type DurableReducerOptionsV2 = {
|
|
|
26
27
|
export type TrustedNetworkV2DurablePolicyReducerOptions = DurableReducerOptionsV2 & {
|
|
27
28
|
store: CrashSafePolicyAnchorStoreV2;
|
|
28
29
|
};
|
|
30
|
+
export type PolicyEntryCidResolverV2 = (policyEntryCid: string, options: {
|
|
31
|
+
signal: AbortSignal;
|
|
32
|
+
}) => Uint8Array | undefined | Promise<Uint8Array | undefined>;
|
|
33
|
+
export type ExactPolicyHeadRequirementV2 = {
|
|
34
|
+
policyEntryCid: string;
|
|
35
|
+
maxAncestrySteps?: number;
|
|
36
|
+
/** Queue-inclusive relative deadline; callers may only shorten the 10s cap. */
|
|
37
|
+
timeoutMs?: number;
|
|
38
|
+
deadline?: number;
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
};
|
|
41
|
+
export type ExactPolicyHeadLeaseV2 = {
|
|
42
|
+
policyEntryCid: string;
|
|
43
|
+
policy: PolicyHeadProjectionV2;
|
|
44
|
+
};
|
|
29
45
|
export type PolicyLeaseReferenceV2 = {
|
|
30
46
|
sequence: bigint;
|
|
31
47
|
digest: Uint8Array;
|
|
@@ -62,6 +78,11 @@ export declare class TrustedNetworkV2DurablePolicyReducer {
|
|
|
62
78
|
private checkpoint?;
|
|
63
79
|
private core;
|
|
64
80
|
private published;
|
|
81
|
+
private readonly descriptor;
|
|
82
|
+
private readonly resolvePolicyEntryByCid?;
|
|
83
|
+
private readonly leaseLifecycleController;
|
|
84
|
+
private readonly disposeLifecycleObservation;
|
|
85
|
+
private exactAuthenticationInFlight?;
|
|
65
86
|
private durableCoreIdentityBytes?;
|
|
66
87
|
private operationTail;
|
|
67
88
|
private authorizationFences;
|
|
@@ -106,6 +127,18 @@ export declare class TrustedNetworkV2DurablePolicyReducer {
|
|
|
106
127
|
* throw/rejection is propagated unchanged, but never halts the policy anchor.
|
|
107
128
|
*/
|
|
108
129
|
withAcceptedPolicyLease<T>(reference: PolicyLeaseReferenceV2, use: (lease: AcceptedPolicyLeaseV2) => T | Promise<T>): Promise<PolicyLeaseResultV2<T>>;
|
|
130
|
+
/**
|
|
131
|
+
* Run one callback while a CID-addressed policy wrapper is authenticated and
|
|
132
|
+
* its policy-body identity is the stable durable current head.
|
|
133
|
+
*
|
|
134
|
+
* Exact fetch, prepared admission, crash-safe publication, and callback share
|
|
135
|
+
* one bounded budget and serialized queue slot. No digest-resolver fallback is
|
|
136
|
+
* allowed when the CID resolver is absent or unavailable.
|
|
137
|
+
*/
|
|
138
|
+
withExactPolicyHead<T>(requirement: ExactPolicyHeadRequirementV2, use: (lease: ExactPolicyHeadLeaseV2) => T | Promise<T>): Promise<PolicyLeaseResultV2<T>>;
|
|
139
|
+
private withSerializedPolicyLease;
|
|
140
|
+
private resolveExactPolicyEntry;
|
|
141
|
+
private authenticateExactPolicyEntry;
|
|
109
142
|
private forkFailStopResult;
|
|
110
143
|
private lifecycleHaltedResult;
|
|
111
144
|
private immediateLeaseHaltedResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v2-policy-anchor.d.ts","sourceRoot":"","sources":["../../src/v2-policy-anchor.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAEhF,OAAO,EAAE,aAAa,EAAc,MAAM,iBAAiB,CAAC;AAE5D,OAAO,KAAK,EACX,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EAEtB,wBAAwB,
|
|
1
|
+
{"version":3,"file":"v2-policy-anchor.d.ts","sourceRoot":"","sources":["../../src/v2-policy-anchor.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,7 +36,7 @@ import { deserialize, field, fixedArray, serialize, variant, vec, } from "@dao-x
|
|
|
36
36
|
import { CrashSafeTwoSlotCheckpoint } from "@peerbit/any-store/checkpoint";
|
|
37
37
|
import { PublicSignKey, sha256Sync } from "@peerbit/crypto";
|
|
38
38
|
import { compare, concat, equals } from "uint8arrays";
|
|
39
|
-
import { TRUSTED_NETWORK_V2_MAX_PENDING_POLICIES, TrustedNetworkV2PolicyReducer, authenticatePolicySnapshotEntryV2, } from "./v2-policy-engine.js";
|
|
39
|
+
import { TRUSTED_NETWORK_V2_MAX_PENDING_POLICIES, TrustedNetworkV2PolicyReducer, authenticatePolicySnapshotEntryV2, captureCanonicalPolicyEntryCidV2, observeAbortSignalV2, } from "./v2-policy-engine.js";
|
|
40
40
|
import { NetworkDescriptorV2, PolicySubjectBindingV2, TRUSTED_NETWORK_V2_KNOWN_ROLE_BITS, TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES, assertNetworkDescriptorV2, } from "./v2.js";
|
|
41
41
|
/**
|
|
42
42
|
* Internal crash-safe policy-anchor storage format.
|
|
@@ -62,6 +62,8 @@ export const TRUSTED_NETWORK_V2_MAX_POLICY_ANCHOR_CHECKPOINT_PAYLOAD_BYTES = (MA
|
|
|
62
62
|
MAX_CHECKPOINT_PAYLOAD_FRAMING_BYTES_V2;
|
|
63
63
|
const MAX_QUEUED_POLICY_INPUT_BYTES_V2 = TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES * 2;
|
|
64
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 = TRUSTED_NETWORK_V2_MAX_PENDING_POLICIES;
|
|
65
67
|
const ZERO_DIGEST = new Uint8Array(32);
|
|
66
68
|
const textEncoder = new TextEncoder();
|
|
67
69
|
const CHECKPOINT_SCOPE_DOMAIN = textEncoder.encode("peerbit/trusted-network/v2/policy-anchor/checkpoint/v1\0");
|
|
@@ -83,6 +85,7 @@ const isAbortSignalV2 = (value) => {
|
|
|
83
85
|
return false;
|
|
84
86
|
}
|
|
85
87
|
};
|
|
88
|
+
const boundedDependencyReasonV2 = (error) => (error instanceof Error ? error.message : String(error)).slice(0, MAX_UNAVAILABLE_REASON_LENGTH);
|
|
86
89
|
/**
|
|
87
90
|
* One self-contained application snapshot. The enclosing generic checkpoint
|
|
88
91
|
* supplies generation, scope, predecessor, and checksum authentication.
|
|
@@ -543,6 +546,11 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
543
546
|
checkpoint;
|
|
544
547
|
core;
|
|
545
548
|
published;
|
|
549
|
+
descriptor;
|
|
550
|
+
resolvePolicyEntryByCid;
|
|
551
|
+
leaseLifecycleController;
|
|
552
|
+
disposeLifecycleObservation;
|
|
553
|
+
exactAuthenticationInFlight;
|
|
546
554
|
durableCoreIdentityBytes;
|
|
547
555
|
operationTail = Promise.resolve();
|
|
548
556
|
authorizationFences = 0;
|
|
@@ -553,6 +561,10 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
553
561
|
this.checkpoint = properties.checkpoint;
|
|
554
562
|
this.core = properties.core;
|
|
555
563
|
this.published = publishedFromReducer(this.core);
|
|
564
|
+
this.descriptor = properties.descriptor;
|
|
565
|
+
this.resolvePolicyEntryByCid = properties.resolvePolicyEntryByCid;
|
|
566
|
+
this.leaseLifecycleController = properties.lifecycle[0];
|
|
567
|
+
this.disposeLifecycleObservation = properties.lifecycle[1];
|
|
556
568
|
this.durableCoreIdentityBytes =
|
|
557
569
|
properties.durableCoreIdentityBytes === undefined
|
|
558
570
|
? undefined
|
|
@@ -560,8 +572,13 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
560
572
|
}
|
|
561
573
|
static async open(options) {
|
|
562
574
|
assertNetworkDescriptorV2(options.descriptor);
|
|
575
|
+
if (options.resolvePolicyEntryByCid !== undefined &&
|
|
576
|
+
typeof options.resolvePolicyEntryByCid !== "function") {
|
|
577
|
+
throw new TypeError("Policy CID resolver must be a function");
|
|
578
|
+
}
|
|
563
579
|
const descriptor = deserialize(serialize(options.descriptor), NetworkDescriptorV2);
|
|
564
|
-
const
|
|
580
|
+
const lifecycle = observeAbortSignalV2(options.signal);
|
|
581
|
+
const signal = lifecycle[0].signal;
|
|
565
582
|
const store = options.store;
|
|
566
583
|
const coreProperties = {
|
|
567
584
|
descriptor,
|
|
@@ -571,32 +588,35 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
571
588
|
maxPending: options.maxPending,
|
|
572
589
|
maxPendingPolicyBytes: options.maxPendingPolicyBytes,
|
|
573
590
|
};
|
|
574
|
-
assertOpenNotAborted(signal);
|
|
575
|
-
const checkpoint = await CrashSafeTwoSlotCheckpoint.open({
|
|
576
|
-
store,
|
|
577
|
-
scope: checkpointScope(descriptor),
|
|
578
|
-
maxPayloadBytes: TRUSTED_NETWORK_V2_MAX_POLICY_ANCHOR_CHECKPOINT_PAYLOAD_BYTES,
|
|
579
|
-
});
|
|
580
|
-
assertOpenNotAborted(signal);
|
|
581
|
-
// Never let the old append-only format look like an empty or newer
|
|
582
|
-
// checkpoint. It was internal and never activated, so migration or fallback
|
|
583
|
-
// would add rollback surface without preserving a public compatibility need.
|
|
584
|
-
for await (const [key] of store.iterator()) {
|
|
585
|
-
assertOpenNotAborted(signal);
|
|
586
|
-
if (key === TRUSTED_NETWORK_V2_POLICY_ANCHOR_STORE_OWNER ||
|
|
587
|
-
key.startsWith(`${TRUSTED_NETWORK_V2_POLICY_ANCHOR_STORE_OWNER}/`)) {
|
|
588
|
-
throw new Error("Legacy append-only policy-anchor records are not supported; reset the dedicated store");
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
assertOpenNotAborted(signal);
|
|
592
|
-
const current = checkpoint.current;
|
|
593
591
|
let core;
|
|
594
592
|
try {
|
|
593
|
+
assertOpenNotAborted(signal);
|
|
594
|
+
const checkpoint = await CrashSafeTwoSlotCheckpoint.open({
|
|
595
|
+
store,
|
|
596
|
+
scope: checkpointScope(descriptor),
|
|
597
|
+
maxPayloadBytes: TRUSTED_NETWORK_V2_MAX_POLICY_ANCHOR_CHECKPOINT_PAYLOAD_BYTES,
|
|
598
|
+
});
|
|
599
|
+
assertOpenNotAborted(signal);
|
|
600
|
+
// Never let the old append-only format look like an empty or newer
|
|
601
|
+
// checkpoint. It was internal and never activated, so migration or fallback
|
|
602
|
+
// would add rollback surface without preserving a public compatibility need.
|
|
603
|
+
for await (const [key] of store.iterator()) {
|
|
604
|
+
assertOpenNotAborted(signal);
|
|
605
|
+
if (key === TRUSTED_NETWORK_V2_POLICY_ANCHOR_STORE_OWNER ||
|
|
606
|
+
key.startsWith(`${TRUSTED_NETWORK_V2_POLICY_ANCHOR_STORE_OWNER}/`)) {
|
|
607
|
+
throw new Error("Legacy append-only policy-anchor records are not supported; reset the dedicated store");
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
assertOpenNotAborted(signal);
|
|
611
|
+
const current = checkpoint.current;
|
|
595
612
|
if (current === undefined) {
|
|
596
613
|
core = new TrustedNetworkV2PolicyReducer(coreProperties);
|
|
597
614
|
return new TrustedNetworkV2DurablePolicyReducer({
|
|
598
615
|
checkpoint,
|
|
599
616
|
core,
|
|
617
|
+
descriptor,
|
|
618
|
+
resolvePolicyEntryByCid: options.resolvePolicyEntryByCid,
|
|
619
|
+
lifecycle,
|
|
600
620
|
});
|
|
601
621
|
}
|
|
602
622
|
const payload = decodeCanonicalCheckpointPayload(current.payload);
|
|
@@ -614,11 +634,15 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
614
634
|
// storage, so release that potentially 8.78 MiB snapshot immediately.
|
|
615
635
|
checkpoint: forked ? undefined : checkpoint,
|
|
616
636
|
core,
|
|
637
|
+
descriptor,
|
|
638
|
+
resolvePolicyEntryByCid: options.resolvePolicyEntryByCid,
|
|
639
|
+
lifecycle,
|
|
617
640
|
durableCoreIdentityBytes: decoded.coreIdentityBytes,
|
|
618
641
|
});
|
|
619
642
|
}
|
|
620
643
|
catch (error) {
|
|
621
644
|
core?.abort();
|
|
645
|
+
lifecycle[1]();
|
|
622
646
|
throw error;
|
|
623
647
|
}
|
|
624
648
|
}
|
|
@@ -685,6 +709,7 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
685
709
|
}
|
|
686
710
|
abort() {
|
|
687
711
|
this.core.abort();
|
|
712
|
+
this.disposeLifecycleObservation();
|
|
688
713
|
}
|
|
689
714
|
ingest(entryBytes) {
|
|
690
715
|
if (this.terminalError !== undefined) {
|
|
@@ -842,37 +867,190 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
842
867
|
reason: "Policy lease callback must be a function",
|
|
843
868
|
});
|
|
844
869
|
}
|
|
845
|
-
|
|
846
|
-
|
|
870
|
+
let digest;
|
|
871
|
+
try {
|
|
872
|
+
digest = copyBytes(suppliedDigest);
|
|
873
|
+
}
|
|
874
|
+
catch {
|
|
875
|
+
return Promise.resolve({
|
|
876
|
+
status: "rejected",
|
|
877
|
+
reason: "Policy reference digest must contain exactly 32 bytes",
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
return this.withSerializedPolicyLease(32, deadline, signal, async (leaseSignal) => {
|
|
881
|
+
const resolution = await this.core.resolveAcceptedPolicyPrefix({ sequence, digest }, { maxSteps, deadline, signal: leaseSignal });
|
|
882
|
+
return resolution.status === "resolved"
|
|
883
|
+
? {
|
|
884
|
+
status: "resolved",
|
|
885
|
+
lease: {
|
|
886
|
+
policy: resolution.policy,
|
|
887
|
+
acceptedHead: resolution.acceptedHead,
|
|
888
|
+
},
|
|
889
|
+
}
|
|
890
|
+
: resolution;
|
|
891
|
+
}, use);
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Run one callback while a CID-addressed policy wrapper is authenticated and
|
|
895
|
+
* its policy-body identity is the stable durable current head.
|
|
896
|
+
*
|
|
897
|
+
* Exact fetch, prepared admission, crash-safe publication, and callback share
|
|
898
|
+
* one bounded budget and serialized queue slot. No digest-resolver fallback is
|
|
899
|
+
* allowed when the CID resolver is absent or unavailable.
|
|
900
|
+
*/
|
|
901
|
+
withExactPolicyHead(requirement, use) {
|
|
902
|
+
const halted = this.immediateLeaseHaltedResult();
|
|
903
|
+
if (halted !== undefined)
|
|
904
|
+
return Promise.resolve(halted);
|
|
905
|
+
let policyEntryCid;
|
|
906
|
+
let maxAncestrySteps;
|
|
907
|
+
let deadline;
|
|
908
|
+
let signal;
|
|
909
|
+
try {
|
|
910
|
+
policyEntryCid = captureCanonicalPolicyEntryCidV2(requirement.policyEntryCid);
|
|
911
|
+
maxAncestrySteps =
|
|
912
|
+
requirement.maxAncestrySteps ??
|
|
913
|
+
DEFAULT_EXACT_POLICY_HEAD_MAX_ANCESTRY_STEPS_V2;
|
|
914
|
+
if (!Number.isSafeInteger(maxAncestrySteps) ||
|
|
915
|
+
maxAncestrySteps < 0 ||
|
|
916
|
+
maxAncestrySteps > TRUSTED_NETWORK_V2_MAX_PENDING_POLICIES) {
|
|
917
|
+
return Promise.resolve({
|
|
918
|
+
status: "rejected",
|
|
919
|
+
reason: `Exact policy maxAncestrySteps must be between 0 and ${TRUSTED_NETWORK_V2_MAX_PENDING_POLICIES}`,
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
const suppliedDeadline = requirement.deadline;
|
|
923
|
+
if (suppliedDeadline !== undefined &&
|
|
924
|
+
(!Number.isSafeInteger(suppliedDeadline) || suppliedDeadline < 0)) {
|
|
925
|
+
return Promise.resolve({
|
|
926
|
+
status: "rejected",
|
|
927
|
+
reason: "Exact policy deadline must be a non-negative safe integer",
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
const timeoutMs = requirement.timeoutMs ?? DEFAULT_EXACT_POLICY_HEAD_TIMEOUT_MS_V2;
|
|
931
|
+
if (!Number.isSafeInteger(timeoutMs) ||
|
|
932
|
+
timeoutMs < 0 ||
|
|
933
|
+
timeoutMs > DEFAULT_EXACT_POLICY_HEAD_TIMEOUT_MS_V2) {
|
|
934
|
+
return Promise.resolve({
|
|
935
|
+
status: "rejected",
|
|
936
|
+
reason: `Exact policy timeoutMs must be between 0 and ${DEFAULT_EXACT_POLICY_HEAD_TIMEOUT_MS_V2}`,
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
const relativeDeadline = Math.min(Number.MAX_SAFE_INTEGER, Date.now() + timeoutMs);
|
|
940
|
+
deadline =
|
|
941
|
+
suppliedDeadline === undefined
|
|
942
|
+
? relativeDeadline
|
|
943
|
+
: Math.min(suppliedDeadline, relativeDeadline);
|
|
944
|
+
signal = requirement.signal;
|
|
945
|
+
if (signal !== undefined && !isAbortSignalV2(signal)) {
|
|
946
|
+
return Promise.resolve({
|
|
947
|
+
status: "rejected",
|
|
948
|
+
reason: "Exact policy signal must be an AbortSignal",
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
catch {
|
|
953
|
+
return Promise.resolve({
|
|
954
|
+
status: "rejected",
|
|
955
|
+
reason: "Exact policy-head requirement is invalid",
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
if (typeof use !== "function") {
|
|
959
|
+
return Promise.resolve({
|
|
960
|
+
status: "rejected",
|
|
961
|
+
reason: "Exact policy-head callback must be a function",
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
return this.withSerializedPolicyLease(textEncoder.encode(policyEntryCid).byteLength, deadline, signal, async (leaseSignal, mutationCompleted) => {
|
|
965
|
+
const exact = await this.resolveExactPolicyEntry(policyEntryCid, deadline, leaseSignal);
|
|
966
|
+
if (exact.status !== "resolved")
|
|
967
|
+
return exact;
|
|
968
|
+
this.authorizationFences += 1;
|
|
969
|
+
try {
|
|
970
|
+
const admission = await this.core.ingestPreparedExactPolicy(exact.lease, {
|
|
971
|
+
maxParentEdges: maxAncestrySteps,
|
|
972
|
+
deadline,
|
|
973
|
+
signal: leaseSignal,
|
|
974
|
+
});
|
|
975
|
+
// Core admission may now be mutated. From here through checkpoint
|
|
976
|
+
// settlement, cancellation may settle outward but cannot release the slot.
|
|
977
|
+
mutationCompleted();
|
|
978
|
+
await this.persistCorePublication(admission);
|
|
979
|
+
const halted = this.immediateLeaseHaltedResult();
|
|
980
|
+
if (halted !== undefined)
|
|
981
|
+
return halted;
|
|
982
|
+
if (admission.status !== "accepted" &&
|
|
983
|
+
admission.status !== "duplicate") {
|
|
984
|
+
return {
|
|
985
|
+
status: admission.status === "rejected" ||
|
|
986
|
+
admission.status === "capacity"
|
|
987
|
+
? admission.status
|
|
988
|
+
: "unavailable",
|
|
989
|
+
reason: admission.reason ?? "Exact policy admission is unavailable",
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
const head = this.published.head;
|
|
993
|
+
if (this.published.state !== "ACTIVE" ||
|
|
994
|
+
head === undefined ||
|
|
995
|
+
head.sequence !== exact.lease.policy.sequence ||
|
|
996
|
+
!equals(head.digest, exact.lease.policy.digest)) {
|
|
997
|
+
return {
|
|
998
|
+
status: "unavailable",
|
|
999
|
+
reason: "Exact policy entry is not the durable current policy head",
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
return {
|
|
1003
|
+
status: "resolved",
|
|
1004
|
+
lease: { policyEntryCid, policy: copyHead(head) },
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
catch (error) {
|
|
1008
|
+
throw this.halt(error);
|
|
1009
|
+
}
|
|
1010
|
+
finally {
|
|
1011
|
+
this.authorizationFences -= 1;
|
|
1012
|
+
}
|
|
1013
|
+
}, use);
|
|
1014
|
+
}
|
|
1015
|
+
withSerializedPolicyLease(retainedInputBytes, deadline, signal, acquire, use) {
|
|
1016
|
+
if (!this.reserveOperation(retainedInputBytes)) {
|
|
847
1017
|
return Promise.resolve({
|
|
848
1018
|
status: "capacity",
|
|
849
1019
|
reason: "Durable policy operation queue is at its fixed capacity",
|
|
850
1020
|
});
|
|
851
1021
|
}
|
|
852
|
-
let
|
|
1022
|
+
let observedSignal;
|
|
853
1023
|
try {
|
|
854
|
-
|
|
1024
|
+
observedSignal = observeAbortSignalV2(signal);
|
|
855
1025
|
}
|
|
856
1026
|
catch {
|
|
857
|
-
this.releaseOperation(
|
|
1027
|
+
this.releaseOperation(retainedInputBytes);
|
|
858
1028
|
return Promise.resolve({
|
|
859
1029
|
status: "rejected",
|
|
860
|
-
reason: "Policy
|
|
1030
|
+
reason: "Policy lease signal could not be observed",
|
|
861
1031
|
});
|
|
862
1032
|
}
|
|
863
1033
|
let queueAcquired = false;
|
|
864
1034
|
let callbackAcquired = false;
|
|
1035
|
+
let mutationCompleted = false;
|
|
1036
|
+
const acquisitionController = observedSignal[0];
|
|
1037
|
+
const interruptionSignal = AbortSignal.any([
|
|
1038
|
+
acquisitionController.signal,
|
|
1039
|
+
this.leaseLifecycleController.signal,
|
|
1040
|
+
]);
|
|
865
1041
|
let preAcquisitionResult;
|
|
866
1042
|
let resolveEarly;
|
|
867
1043
|
const earlyResult = new Promise((resolve) => {
|
|
868
1044
|
resolveEarly = resolve;
|
|
869
1045
|
});
|
|
870
1046
|
let deadlineTimer;
|
|
871
|
-
|
|
872
|
-
|
|
1047
|
+
const interruptBeforeAcquisition = () => {
|
|
1048
|
+
const halted = this.leaseLifecycleController.signal.aborted;
|
|
873
1049
|
settleBeforeAcquisition({
|
|
874
|
-
status: "unavailable",
|
|
875
|
-
reason:
|
|
1050
|
+
status: halted ? "halted" : "unavailable",
|
|
1051
|
+
reason: halted
|
|
1052
|
+
? "Policy reducer lifecycle is aborted"
|
|
1053
|
+
: "Policy lease acquisition was aborted by the caller",
|
|
876
1054
|
});
|
|
877
1055
|
};
|
|
878
1056
|
const cleanupWakeups = () => {
|
|
@@ -880,29 +1058,26 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
880
1058
|
clearTimeout(deadlineTimer);
|
|
881
1059
|
deadlineTimer = undefined;
|
|
882
1060
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
try {
|
|
886
|
-
signal?.removeEventListener("abort", abortBeforeAcquisition);
|
|
887
|
-
}
|
|
888
|
-
catch {
|
|
889
|
-
// A structurally valid hostile signal must not break queue cleanup.
|
|
890
|
-
}
|
|
891
|
-
}
|
|
1061
|
+
interruptionSignal.removeEventListener("abort", interruptBeforeAcquisition);
|
|
1062
|
+
observedSignal[1]();
|
|
892
1063
|
};
|
|
893
1064
|
function settleBeforeAcquisition(result) {
|
|
894
|
-
if (
|
|
895
|
-
callbackAcquired ||
|
|
896
|
-
preAcquisitionResult !== undefined) {
|
|
1065
|
+
if (callbackAcquired || preAcquisitionResult !== undefined) {
|
|
897
1066
|
return;
|
|
898
1067
|
}
|
|
899
1068
|
preAcquisitionResult = result;
|
|
1069
|
+
acquisitionController.abort();
|
|
900
1070
|
cleanupWakeups();
|
|
901
|
-
|
|
1071
|
+
if (!queueAcquired || mutationCompleted)
|
|
1072
|
+
resolveEarly(result);
|
|
902
1073
|
}
|
|
1074
|
+
const completeMutation = () => {
|
|
1075
|
+
mutationCompleted = true;
|
|
1076
|
+
if (preAcquisitionResult !== undefined)
|
|
1077
|
+
resolveEarly(preAcquisitionResult);
|
|
1078
|
+
};
|
|
903
1079
|
const armDeadlineWakeup = () => {
|
|
904
1080
|
if (deadline === undefined ||
|
|
905
|
-
queueAcquired ||
|
|
906
1081
|
callbackAcquired ||
|
|
907
1082
|
preAcquisitionResult !== undefined) {
|
|
908
1083
|
return;
|
|
@@ -933,26 +1108,22 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
933
1108
|
return preAcquisitionResult;
|
|
934
1109
|
}
|
|
935
1110
|
queueAcquired = true;
|
|
936
|
-
cleanupWakeups();
|
|
937
1111
|
const queuedHalt = this.immediateLeaseHaltedResult();
|
|
938
1112
|
if (queuedHalt !== undefined)
|
|
939
1113
|
return queuedHalt;
|
|
940
|
-
const
|
|
941
|
-
sequence,
|
|
942
|
-
digest,
|
|
943
|
-
}, { maxSteps, deadline, signal });
|
|
1114
|
+
const acquisition = await acquire(acquisitionController.signal, completeMutation);
|
|
944
1115
|
if (preAcquisitionResult !== undefined) {
|
|
945
1116
|
return preAcquisitionResult;
|
|
946
1117
|
}
|
|
947
|
-
if (
|
|
948
|
-
return
|
|
1118
|
+
if (acquisition.status !== "resolved")
|
|
1119
|
+
return acquisition;
|
|
949
1120
|
// Lifecycle abort before callback invocation loses acquisition. There is
|
|
950
1121
|
// no await between this check and invoking user code, which is the lease's
|
|
951
1122
|
// linearization point.
|
|
952
1123
|
const resolvedHalt = this.immediateLeaseHaltedResult();
|
|
953
1124
|
if (resolvedHalt !== undefined)
|
|
954
1125
|
return resolvedHalt;
|
|
955
|
-
if (signal
|
|
1126
|
+
if (acquisitionController.signal.aborted) {
|
|
956
1127
|
return {
|
|
957
1128
|
status: "unavailable",
|
|
958
1129
|
reason: "Policy lease acquisition was aborted by the caller",
|
|
@@ -966,37 +1137,138 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
966
1137
|
}
|
|
967
1138
|
callbackAcquired = true;
|
|
968
1139
|
cleanupWakeups();
|
|
969
|
-
const value = await use(
|
|
970
|
-
// The core created these two projections independently and retains
|
|
971
|
-
// neither, including when the requested policy is the current head.
|
|
972
|
-
policy: resolution.policy,
|
|
973
|
-
acceptedHead: resolution.acceptedHead,
|
|
974
|
-
});
|
|
1140
|
+
const value = await use(acquisition.lease);
|
|
975
1141
|
return { status: "completed", value };
|
|
976
1142
|
});
|
|
977
1143
|
const retainedResult = queuedResult.finally(() => {
|
|
978
1144
|
cleanupWakeups();
|
|
979
|
-
this.releaseOperation(
|
|
1145
|
+
this.releaseOperation(retainedInputBytes);
|
|
980
1146
|
});
|
|
981
1147
|
this.operationTail = retainedResult.then(() => { }, () => { });
|
|
982
|
-
|
|
983
|
-
|
|
1148
|
+
interruptionSignal.addEventListener("abort", interruptBeforeAcquisition, {
|
|
1149
|
+
once: true,
|
|
1150
|
+
});
|
|
1151
|
+
if (interruptionSignal.aborted)
|
|
1152
|
+
interruptBeforeAcquisition();
|
|
1153
|
+
armDeadlineWakeup();
|
|
1154
|
+
return Promise.race([retainedResult, earlyResult]);
|
|
1155
|
+
}
|
|
1156
|
+
async resolveExactPolicyEntry(policyEntryCid, deadline, callerSignal) {
|
|
1157
|
+
const resolver = this.resolvePolicyEntryByCid;
|
|
1158
|
+
if (resolver === undefined) {
|
|
1159
|
+
return {
|
|
1160
|
+
status: "unavailable",
|
|
1161
|
+
reason: "Exact policy-head readiness has no policy CID resolver",
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
if (this.core.state === "HALTED") {
|
|
1165
|
+
return {
|
|
1166
|
+
status: "halted",
|
|
1167
|
+
reason: "Policy reducer lifecycle is aborted",
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
if (callerSignal.aborted) {
|
|
1171
|
+
return {
|
|
1172
|
+
status: "unavailable",
|
|
1173
|
+
reason: "Exact policy-head acquisition was aborted by the caller",
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
if (Date.now() >= deadline) {
|
|
1177
|
+
return {
|
|
1178
|
+
status: "unavailable",
|
|
1179
|
+
reason: "Exact policy-head acquisition deadline elapsed",
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
1183
|
+
callerSignal.addEventListener("abort", () => reject(new Error("Exact policy-head resolution interrupted")), { once: true });
|
|
1184
|
+
});
|
|
1185
|
+
const interruptionFailure = () => {
|
|
1186
|
+
const halted = this.immediateLeaseHaltedResult();
|
|
1187
|
+
if (halted !== undefined)
|
|
1188
|
+
return halted;
|
|
1189
|
+
if (callerSignal.aborted) {
|
|
1190
|
+
return {
|
|
1191
|
+
status: "unavailable",
|
|
1192
|
+
reason: "Exact policy-head acquisition was aborted by the caller",
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
if (Date.now() >= deadline) {
|
|
1196
|
+
return {
|
|
1197
|
+
status: "unavailable",
|
|
1198
|
+
reason: "Exact policy-head acquisition deadline elapsed",
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
return undefined;
|
|
1202
|
+
};
|
|
1203
|
+
const priorAuthentication = this.exactAuthenticationInFlight;
|
|
1204
|
+
if (priorAuthentication !== undefined) {
|
|
984
1205
|
try {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1206
|
+
await Promise.race([
|
|
1207
|
+
priorAuthentication.catch(() => { }),
|
|
1208
|
+
interrupted,
|
|
1209
|
+
]);
|
|
988
1210
|
}
|
|
989
1211
|
catch {
|
|
990
|
-
|
|
991
|
-
status: "
|
|
992
|
-
reason: "
|
|
1212
|
+
return (interruptionFailure() ?? {
|
|
1213
|
+
status: "unavailable",
|
|
1214
|
+
reason: "Exact policy authentication slot is unavailable",
|
|
993
1215
|
});
|
|
994
1216
|
}
|
|
995
|
-
|
|
996
|
-
|
|
1217
|
+
const interruptedResult = interruptionFailure();
|
|
1218
|
+
if (interruptedResult !== undefined)
|
|
1219
|
+
return interruptedResult;
|
|
997
1220
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1221
|
+
let entryBytes;
|
|
1222
|
+
try {
|
|
1223
|
+
entryBytes = await Promise.race([
|
|
1224
|
+
Promise.resolve().then(() => resolver(policyEntryCid, { signal: callerSignal })),
|
|
1225
|
+
interrupted,
|
|
1226
|
+
]);
|
|
1227
|
+
}
|
|
1228
|
+
catch (error) {
|
|
1229
|
+
const interruptedResult = interruptionFailure();
|
|
1230
|
+
if (interruptedResult !== undefined)
|
|
1231
|
+
return interruptedResult;
|
|
1232
|
+
return {
|
|
1233
|
+
status: "unavailable",
|
|
1234
|
+
reason: `Policy CID resolver dependency is unavailable: ${boundedDependencyReasonV2(error)}`,
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
if (entryBytes === undefined) {
|
|
1238
|
+
return {
|
|
1239
|
+
status: "unavailable",
|
|
1240
|
+
reason: "Exact policy entry is unavailable from its CID resolver",
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
const authentication = Promise.resolve().then(() => this.authenticateExactPolicyEntry({
|
|
1244
|
+
policyEntryCid,
|
|
1245
|
+
entryBytes,
|
|
1246
|
+
descriptor: this.descriptor,
|
|
1247
|
+
}));
|
|
1248
|
+
this.exactAuthenticationInFlight = authentication;
|
|
1249
|
+
const clearAuthentication = () => {
|
|
1250
|
+
if (this.exactAuthenticationInFlight === authentication) {
|
|
1251
|
+
this.exactAuthenticationInFlight = undefined;
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
void authentication.then(clearAuthentication, clearAuthentication);
|
|
1255
|
+
try {
|
|
1256
|
+
const authenticated = await Promise.race([authentication, interrupted]);
|
|
1257
|
+
const completedInterruption = interruptionFailure();
|
|
1258
|
+
return (completedInterruption ?? { status: "resolved", lease: authenticated });
|
|
1259
|
+
}
|
|
1260
|
+
catch (error) {
|
|
1261
|
+
const interruptedResult = interruptionFailure();
|
|
1262
|
+
if (interruptedResult !== undefined)
|
|
1263
|
+
return interruptedResult;
|
|
1264
|
+
return {
|
|
1265
|
+
status: "rejected",
|
|
1266
|
+
reason: `Exact policy entry is invalid: ${boundedDependencyReasonV2(error)}`,
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
authenticateExactPolicyEntry(properties) {
|
|
1271
|
+
return this.core.prepareExactPolicyEntry(properties.policyEntryCid, properties.entryBytes);
|
|
1000
1272
|
}
|
|
1001
1273
|
forkFailStopResult() {
|
|
1002
1274
|
return {
|
|
@@ -1125,6 +1397,11 @@ export class TrustedNetworkV2DurablePolicyReducer {
|
|
|
1125
1397
|
throw new Error("Policy-anchor checkpoint is unavailable");
|
|
1126
1398
|
}
|
|
1127
1399
|
await checkpoint.commit(payloadBytes);
|
|
1400
|
+
// Atomic replacement is not cancellable. A lifecycle that ended while it
|
|
1401
|
+
// was in flight may leave durable recovery state, but cannot publish it into
|
|
1402
|
+
// the stale in-memory instance.
|
|
1403
|
+
if (this.core.state === "HALTED")
|
|
1404
|
+
return;
|
|
1128
1405
|
this.durableCoreIdentityBytes = nextDurableCoreIdentityBytes;
|
|
1129
1406
|
this.published = nextPublished;
|
|
1130
1407
|
if (durableState.state === "FORKED") {
|