@peerbit/trusted-network 6.0.104 → 6.0.105

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.
@@ -1,5 +1,4 @@
1
1
  import { PublicSignKey } from "@peerbit/crypto";
2
- import { EntryV0 } from "@peerbit/log";
3
2
  import { NetworkDescriptorV2, PolicySnapshotBodyV2, PolicySubjectBindingV2 } from "./v2.js";
4
3
  type ValidatedPolicySnapshotV2 = {
5
4
  body: PolicySnapshotBodyV2;
@@ -8,7 +7,9 @@ type ValidatedPolicySnapshotV2 = {
8
7
  entryBytes: Uint8Array;
9
8
  accountedBytes: number;
10
9
  };
11
- export type PolicySnapshotResolverV2 = (digest: Uint8Array) => EntryV0<Uint8Array> | undefined | Promise<EntryV0<Uint8Array> | undefined>;
10
+ export type PolicySnapshotResolverV2 = (digest: Uint8Array, options: {
11
+ signal: AbortSignal;
12
+ }) => Uint8Array | undefined | Promise<Uint8Array | undefined>;
12
13
  export type PolicyParentFetchHintV2 = {
13
14
  kind: "policy-parent";
14
15
  digest: Uint8Array;
@@ -37,12 +38,16 @@ export type PolicyAdmissionResultV2 = {
37
38
  pendingBytes: number;
38
39
  evictedPolicyDigests?: Uint8Array[];
39
40
  };
40
- export declare const authenticatePolicySnapshotEntryV2: (entry: unknown, descriptor: NetworkDescriptorV2) => Promise<ValidatedPolicySnapshotV2>;
41
+ export declare const authenticatePolicySnapshotEntryV2: (entryBytes: Uint8Array, descriptor: NetworkDescriptorV2) => Promise<ValidatedPolicySnapshotV2>;
41
42
  export declare class TrustedNetworkV2PolicyReducer {
42
43
  private readonly descriptor;
43
44
  private readonly resolvePolicyEntry;
45
+ private readonly resolveTimeoutMs;
44
46
  private readonly maxPending;
45
47
  private readonly maxPendingPolicyBytes;
48
+ private readonly lifecycleController;
49
+ private externalSignal?;
50
+ private externalAbortListener?;
46
51
  private acceptedHead?;
47
52
  private projectedRoles;
48
53
  private readonly pending;
@@ -52,10 +57,12 @@ export declare class TrustedNetworkV2PolicyReducer {
52
57
  constructor(properties: {
53
58
  descriptor: NetworkDescriptorV2;
54
59
  resolvePolicyEntry: PolicySnapshotResolverV2;
60
+ resolveTimeoutMs?: number;
61
+ signal?: AbortSignal;
55
62
  maxPending?: number;
56
63
  maxPendingPolicyBytes?: number;
57
64
  });
58
- get state(): "EMPTY" | "ACTIVE" | "UNAVAILABLE" | "FORKED";
65
+ get state(): "EMPTY" | "ACTIVE" | "UNAVAILABLE" | "FORKED" | "HALTED";
59
66
  get head(): PolicyHeadProjectionV2 | undefined;
60
67
  get forkEvidence(): PolicyForkEvidenceV2 | undefined;
61
68
  get pendingCount(): number;
@@ -63,15 +70,19 @@ export declare class TrustedNetworkV2PolicyReducer {
63
70
  get pendingDigests(): Uint8Array[];
64
71
  rolesFor(subject: PublicSignKey): number;
65
72
  isAuthorized(subject: PublicSignKey, roles: number): boolean;
73
+ abort(): void;
66
74
  private fetchHints;
67
75
  private result;
68
76
  private forkedResult;
77
+ private haltedResult;
69
78
  private unavailableResult;
70
79
  private completedDrainResult;
71
80
  private project;
72
81
  private retainCanonicalHeadEntry;
82
+ private resolveExternalSnapshot;
73
83
  private resolveSnapshot;
74
84
  private parentOf;
85
+ private candidateAncestryResult;
75
86
  private acceptedAncestryUnavailable;
76
87
  private evaluate;
77
88
  private setFork;
@@ -81,7 +92,7 @@ export declare class TrustedNetworkV2PolicyReducer {
81
92
  private enterUnavailable;
82
93
  private drainPending;
83
94
  private enqueueAdmission;
84
- ingest(entry: unknown): Promise<PolicyAdmissionResultV2>;
95
+ ingest(entryBytes: Uint8Array): Promise<PolicyAdmissionResultV2>;
85
96
  retryUnavailable(): Promise<PolicyAdmissionResultV2>;
86
97
  private ingestOne;
87
98
  private retryUnavailableOne;
@@ -1 +1 @@
1
- {"version":3,"file":"v2-policy-engine.d.ts","sourceRoot":"","sources":["../../src/v2-policy-engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"v2-policy-engine.d.ts","sourceRoot":"","sources":["../../src/v2-policy-engine.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import { deserialize, serialize } from "@dao-xyz/borsh";
2
2
  import { DecryptedThing, PublicSignKey, verify } from "@peerbit/crypto";
3
3
  import { Entry, EntryV0, NO_ENCODING } from "@peerbit/log";
4
4
  import { compare, equals } from "uint8arrays";
5
- import { NetworkDescriptorV2, PolicySnapshotBodyV2, PolicySubjectBindingV2, TRUSTED_NETWORK_V2_KNOWN_ROLE_BITS, assertNetworkDescriptorV2, decodePolicySnapshotBodyV2, digestPolicySnapshotBodyV2, } from "./v2.js";
5
+ import { NetworkDescriptorV2, PolicySnapshotBodyV2, PolicySubjectBindingV2, TRUSTED_NETWORK_V2_KNOWN_ROLE_BITS, TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES, assertNetworkDescriptorV2, decodePolicySnapshotBodyV2, digestPolicySnapshotBodyV2, } from "./v2.js";
6
6
  /**
7
7
  * Internal policy reducer for the non-activatable TrustedNetwork v2 scaffold.
8
8
  *
@@ -12,8 +12,12 @@ import { NetworkDescriptorV2, PolicySnapshotBodyV2, PolicySubjectBindingV2, TRUS
12
12
  * resolver instead of being accumulated in memory.
13
13
  */
14
14
  const DEFAULT_MAX_PENDING_POLICIES_V2 = 64;
15
- const DEFAULT_MAX_PENDING_POLICY_BYTES_V2 = 256 * 1024;
16
15
  const PENDING_POLICY_ACCOUNTING_OVERHEAD_V2 = 64;
16
+ const MAX_PENDING_POLICY_ACCOUNTED_BYTES_V2 = TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES * 2 +
17
+ PENDING_POLICY_ACCOUNTING_OVERHEAD_V2;
18
+ const DEFAULT_MAX_PENDING_POLICY_BYTES_V2 = MAX_PENDING_POLICY_ACCOUNTED_BYTES_V2;
19
+ const DEFAULT_POLICY_RESOLUTION_TIMEOUT_MS_V2 = 10 * 1000;
20
+ const MAX_TIMER_DELAY_MS_V2 = 0x7fffffff;
17
21
  const MAX_UNAVAILABLE_REASON_LENGTH_V2 = 512;
18
22
  const copyBytes = (bytes) => Uint8Array.from(bytes);
19
23
  const bytesKey = (bytes) => {
@@ -44,29 +48,47 @@ const copySnapshot = (snapshot) => ({
44
48
  });
45
49
  const validationMessage = (error) => error instanceof Error ? error.message : String(error);
46
50
  const boundedUnavailableReason = (reason) => reason.slice(0, MAX_UNAVAILABLE_REASON_LENGTH_V2);
47
- export const authenticatePolicySnapshotEntryV2 = async (entry, descriptor) => {
48
- assertNetworkDescriptorV2(descriptor);
49
- if (!(entry instanceof EntryV0)) {
51
+ class PolicyDependencyUnavailableErrorV2 extends Error {
52
+ constructor(message) {
53
+ super(message);
54
+ this.name = "PolicyDependencyUnavailableErrorV2";
55
+ }
56
+ }
57
+ const capturePolicySnapshotEntryBytesV2 = (entryBytes) => {
58
+ if (!(entryBytes instanceof Uint8Array)) {
59
+ throw new Error("Policy snapshot must use canonical EntryV0 bytes");
60
+ }
61
+ if (entryBytes.byteLength === 0) {
62
+ throw new Error("Policy snapshot must use EntryV0");
63
+ }
64
+ if (entryBytes.byteLength > TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES) {
65
+ throw new Error(`Policy snapshot entry must contain 1-${TRUSTED_NETWORK_V2_MAX_POLICY_ENTRY_BYTES} bytes`);
66
+ }
67
+ // Apply the protocol byte ceiling before this first copy, decode, or crypto
68
+ // operation. The retained copy also prevents caller mutation during awaits.
69
+ return copyBytes(entryBytes);
70
+ };
71
+ const authenticateCapturedPolicySnapshotEntryV2 = async (canonicalEntryBytes, descriptor) => {
72
+ const authenticatedEntry = deserialize(canonicalEntryBytes, Entry);
73
+ if (!(authenticatedEntry instanceof EntryV0)) {
50
74
  throw new Error("Policy snapshot must use EntryV0");
51
75
  }
52
- if (!(entry._meta instanceof DecryptedThing)) {
76
+ if (!equals(canonicalEntryBytes, serialize(authenticatedEntry))) {
77
+ throw new Error("Policy snapshot entry encoding is not canonical");
78
+ }
79
+ if (!(authenticatedEntry._meta instanceof DecryptedThing)) {
53
80
  throw new Error("Policy snapshot metadata must be public");
54
81
  }
55
- if (!(entry._payload instanceof DecryptedThing)) {
82
+ if (!(authenticatedEntry._payload instanceof DecryptedThing)) {
56
83
  throw new Error("Policy snapshot payload must be public");
57
84
  }
58
- if (entry._signatures === undefined ||
59
- entry._signatures.signatures.length !== 1) {
85
+ if (authenticatedEntry._signatures === undefined ||
86
+ authenticatedEntry._signatures.signatures.length !== 1) {
60
87
  throw new Error("Policy snapshot must contain exactly one signature");
61
88
  }
62
- if (!(entry._signatures.signatures[0] instanceof DecryptedThing)) {
89
+ if (!(authenticatedEntry._signatures.signatures[0] instanceof DecryptedThing)) {
63
90
  throw new Error("Policy snapshot signature must be public");
64
91
  }
65
- const entryBytes = serialize(entry);
66
- const authenticatedEntry = deserialize(entryBytes, Entry);
67
- if (!(authenticatedEntry instanceof EntryV0)) {
68
- throw new Error("Policy snapshot must decode as EntryV0");
69
- }
70
92
  authenticatedEntry.init({ encoding: NO_ENCODING });
71
93
  const signatures = await authenticatedEntry.getSignatures();
72
94
  if (signatures.length !== 1) {
@@ -89,12 +111,16 @@ export const authenticatePolicySnapshotEntryV2 = async (entry, descriptor) => {
89
111
  body: copyBody(body),
90
112
  digest: copyBytes(digest),
91
113
  digestKey: bytesKey(digest),
92
- entryBytes,
93
- accountedBytes: entryBytes.byteLength +
114
+ entryBytes: canonicalEntryBytes,
115
+ accountedBytes: canonicalEntryBytes.byteLength +
94
116
  serialize(body).byteLength +
95
117
  PENDING_POLICY_ACCOUNTING_OVERHEAD_V2,
96
118
  };
97
119
  };
120
+ export const authenticatePolicySnapshotEntryV2 = async (entryBytes, descriptor) => {
121
+ assertNetworkDescriptorV2(descriptor);
122
+ return authenticateCapturedPolicySnapshotEntryV2(capturePolicySnapshotEntryBytesV2(entryBytes), descriptor);
123
+ };
98
124
  const projectionFromSnapshot = (snapshot) => ({
99
125
  sequence: snapshot.body.sequence,
100
126
  digest: copyBytes(snapshot.digest),
@@ -131,8 +157,12 @@ const copyForkEvidence = (evidence) => ({
131
157
  export class TrustedNetworkV2PolicyReducer {
132
158
  descriptor;
133
159
  resolvePolicyEntry;
160
+ resolveTimeoutMs;
134
161
  maxPending;
135
162
  maxPendingPolicyBytes;
163
+ lifecycleController = new AbortController();
164
+ externalSignal;
165
+ externalAbortListener;
136
166
  acceptedHead;
137
167
  projectedRoles = new Map();
138
168
  pending = new Map();
@@ -149,15 +179,37 @@ export class TrustedNetworkV2PolicyReducer {
149
179
  }
150
180
  const maxPendingPolicyBytes = properties.maxPendingPolicyBytes ?? DEFAULT_MAX_PENDING_POLICY_BYTES_V2;
151
181
  if (!Number.isSafeInteger(maxPendingPolicyBytes) ||
152
- maxPendingPolicyBytes < 1) {
153
- throw new Error("maxPendingPolicyBytes must be a positive safe integer");
182
+ maxPendingPolicyBytes < 1 ||
183
+ maxPendingPolicyBytes > MAX_PENDING_POLICY_ACCOUNTED_BYTES_V2) {
184
+ throw new Error(`maxPendingPolicyBytes must be a positive safe integer no greater than ${MAX_PENDING_POLICY_ACCOUNTED_BYTES_V2}`);
185
+ }
186
+ const resolveTimeoutMs = properties.resolveTimeoutMs ?? DEFAULT_POLICY_RESOLUTION_TIMEOUT_MS_V2;
187
+ if (!Number.isSafeInteger(resolveTimeoutMs) ||
188
+ resolveTimeoutMs < 1 ||
189
+ resolveTimeoutMs > MAX_TIMER_DELAY_MS_V2) {
190
+ throw new Error(`resolveTimeoutMs must be a positive safe integer no greater than ${MAX_TIMER_DELAY_MS_V2}`);
154
191
  }
155
192
  this.descriptor = deserialize(serialize(properties.descriptor), NetworkDescriptorV2);
156
193
  this.resolvePolicyEntry = properties.resolvePolicyEntry;
194
+ this.resolveTimeoutMs = resolveTimeoutMs;
157
195
  this.maxPending = maxPending;
158
196
  this.maxPendingPolicyBytes = maxPendingPolicyBytes;
197
+ if (properties.signal?.aborted) {
198
+ this.lifecycleController.abort();
199
+ }
200
+ else if (properties.signal !== undefined) {
201
+ this.externalSignal = properties.signal;
202
+ this.externalAbortListener = () => {
203
+ this.externalSignal = undefined;
204
+ this.externalAbortListener = undefined;
205
+ this.lifecycleController.abort();
206
+ };
207
+ this.externalSignal.addEventListener("abort", this.externalAbortListener, { once: true });
208
+ }
159
209
  }
160
210
  get state() {
211
+ if (this.lifecycleController.signal.aborted)
212
+ return "HALTED";
161
213
  if (this.fork !== undefined)
162
214
  return "FORKED";
163
215
  if (this.unavailable !== undefined)
@@ -199,6 +251,15 @@ export class TrustedNetworkV2PolicyReducer {
199
251
  }
200
252
  return (this.rolesFor(subject) & roles) === roles;
201
253
  }
254
+ abort() {
255
+ if (this.externalSignal !== undefined &&
256
+ this.externalAbortListener !== undefined) {
257
+ this.externalSignal.removeEventListener("abort", this.externalAbortListener);
258
+ }
259
+ this.externalSignal = undefined;
260
+ this.externalAbortListener = undefined;
261
+ this.lifecycleController.abort();
262
+ }
202
263
  fetchHints() {
203
264
  const unique = new Map();
204
265
  for (const { missingParentDigest } of this.pending.values()) {
@@ -230,6 +291,9 @@ export class TrustedNetworkV2PolicyReducer {
230
291
  forkedResult() {
231
292
  return this.result("forked", "Policy authority signed competing children");
232
293
  }
294
+ haltedResult() {
295
+ return this.result("halted", "Policy reducer lifecycle is aborted");
296
+ }
233
297
  unavailableResult(retention) {
234
298
  const blockedCandidateRetained = this.unavailable !== undefined &&
235
299
  this.pending.has(this.unavailable.candidateDigestKey);
@@ -244,6 +308,8 @@ export class TrustedNetworkV2PolicyReducer {
244
308
  completedDrainResult(outcome) {
245
309
  if (outcome?.status === "forked")
246
310
  return this.forkedResult();
311
+ if (outcome?.status === "halted")
312
+ return this.haltedResult();
247
313
  return outcome?.status === "unavailable"
248
314
  ? this.unavailableResult(outcome)
249
315
  : undefined;
@@ -261,6 +327,67 @@ export class TrustedNetworkV2PolicyReducer {
261
327
  this.acceptedHead = copySnapshot(snapshot);
262
328
  }
263
329
  }
330
+ async resolveExternalSnapshot(digest) {
331
+ if (this.lifecycleController.signal.aborted) {
332
+ throw new PolicyDependencyUnavailableErrorV2("Policy resolver lifecycle is aborted");
333
+ }
334
+ const attemptController = new AbortController();
335
+ let timedOut = false;
336
+ const abortFromLifecycle = () => attemptController.abort();
337
+ this.lifecycleController.signal.addEventListener("abort", abortFromLifecycle, { once: true });
338
+ const timeout = setTimeout(() => {
339
+ timedOut = true;
340
+ attemptController.abort();
341
+ }, this.resolveTimeoutMs);
342
+ const abortError = () => new PolicyDependencyUnavailableErrorV2(timedOut
343
+ ? `Policy resolver timed out after ${this.resolveTimeoutMs} ms`
344
+ : "Policy resolver attempt was aborted");
345
+ let rejectOnAbort;
346
+ const abortPromise = new Promise((_resolve, reject) => {
347
+ rejectOnAbort = () => {
348
+ reject(abortError());
349
+ };
350
+ attemptController.signal.addEventListener("abort", rejectOnAbort, {
351
+ once: true,
352
+ });
353
+ });
354
+ const resolution = Promise.resolve().then(async () => {
355
+ if (attemptController.signal.aborted)
356
+ throw abortError();
357
+ const entryBytes = await this.resolvePolicyEntry(copyBytes(digest), {
358
+ signal: attemptController.signal,
359
+ });
360
+ // A resolver may ignore cancellation. Do not spend decode or signature
361
+ // verification work on bytes that arrive after this attempt expired.
362
+ if (attemptController.signal.aborted)
363
+ throw abortError();
364
+ if (entryBytes === undefined)
365
+ return undefined;
366
+ const snapshot = await authenticatePolicySnapshotEntryV2(entryBytes, this.descriptor);
367
+ if (!equals(snapshot.digest, digest)) {
368
+ throw new Error("Policy resolver returned the wrong body digest");
369
+ }
370
+ return snapshot;
371
+ });
372
+ // Promise.race installs handlers, but this explicit observer documents and
373
+ // preserves consumption if the resolver settles after its deadline.
374
+ void resolution.then(() => undefined, () => undefined);
375
+ try {
376
+ return await Promise.race([resolution, abortPromise]);
377
+ }
378
+ catch (error) {
379
+ if (error instanceof PolicyDependencyUnavailableErrorV2)
380
+ throw error;
381
+ throw new PolicyDependencyUnavailableErrorV2(`Policy resolver dependency is unavailable: ${validationMessage(error)}`);
382
+ }
383
+ finally {
384
+ clearTimeout(timeout);
385
+ this.lifecycleController.signal.removeEventListener("abort", abortFromLifecycle);
386
+ if (rejectOnAbort !== undefined) {
387
+ attemptController.signal.removeEventListener("abort", rejectOnAbort);
388
+ }
389
+ }
390
+ }
264
391
  async resolveSnapshot(digest, cache) {
265
392
  const digestKey = bytesKey(digest);
266
393
  if (this.acceptedHead?.digestKey === digestKey) {
@@ -271,16 +398,7 @@ export class TrustedNetworkV2PolicyReducer {
271
398
  return copySnapshot(pending.snapshot);
272
399
  let resolution = cache?.get(digestKey);
273
400
  if (resolution === undefined) {
274
- resolution = (async () => {
275
- const entry = await this.resolvePolicyEntry(copyBytes(digest));
276
- if (entry === undefined)
277
- return undefined;
278
- const snapshot = await authenticatePolicySnapshotEntryV2(entry, this.descriptor);
279
- if (!equals(snapshot.digest, digest)) {
280
- throw new Error("Policy resolver returned the wrong body digest");
281
- }
282
- return snapshot;
283
- })();
401
+ resolution = this.resolveExternalSnapshot(digest);
284
402
  cache?.set(digestKey, resolution);
285
403
  }
286
404
  const snapshot = await resolution;
@@ -300,9 +418,9 @@ export class TrustedNetworkV2PolicyReducer {
300
418
  }
301
419
  catch (error) {
302
420
  return {
303
- status: "reject",
421
+ status: "unavailable",
304
422
  digest: copyBytes(child.body.previousPolicyDigest),
305
- reason: `Policy parent validation failed: ${validationMessage(error)}`,
423
+ reason: `Policy parent dependency is unavailable: ${validationMessage(error)}`,
306
424
  };
307
425
  }
308
426
  if (parent === undefined) {
@@ -320,13 +438,24 @@ export class TrustedNetworkV2PolicyReducer {
320
438
  }
321
439
  return { status: "found", parent };
322
440
  }
441
+ candidateAncestryResult(resolution) {
442
+ return resolution.status === "unavailable"
443
+ ? {
444
+ status: "missing",
445
+ digest: copyBytes(resolution.digest),
446
+ reason: resolution.reason,
447
+ }
448
+ : resolution;
449
+ }
323
450
  acceptedAncestryUnavailable(resolution) {
324
451
  return {
325
452
  status: "unavailable",
326
453
  digest: copyBytes(resolution.digest),
327
454
  reason: resolution.status === "missing"
328
455
  ? "Accepted policy ancestry is unavailable from the resolver"
329
- : `Accepted policy ancestry validation failed: ${resolution.reason}`,
456
+ : resolution.status === "unavailable"
457
+ ? `Accepted policy ancestry is unavailable: ${resolution.reason}`
458
+ : `Accepted policy ancestry validation failed: ${resolution.reason}`,
330
459
  };
331
460
  }
332
461
  async evaluate(candidate) {
@@ -335,8 +464,9 @@ export class TrustedNetworkV2PolicyReducer {
335
464
  let cursor = candidate;
336
465
  while (cursor.body.sequence !== 0n) {
337
466
  const parent = await this.parentOf(cursor, resolutionCache);
338
- if (parent.status !== "found")
339
- return parent;
467
+ if (parent.status !== "found") {
468
+ return this.candidateAncestryResult(parent);
469
+ }
340
470
  cursor = parent.parent;
341
471
  }
342
472
  return { status: "accept" };
@@ -348,8 +478,9 @@ export class TrustedNetworkV2PolicyReducer {
348
478
  while (candidateCursor.body.sequence > acceptedCursor.body.sequence) {
349
479
  candidateChild = candidateCursor;
350
480
  const parent = await this.parentOf(candidateCursor, resolutionCache);
351
- if (parent.status !== "found")
352
- return parent;
481
+ if (parent.status !== "found") {
482
+ return this.candidateAncestryResult(parent);
483
+ }
353
484
  candidateCursor = parent.parent;
354
485
  }
355
486
  while (acceptedCursor.body.sequence > candidateCursor.body.sequence) {
@@ -377,8 +508,9 @@ export class TrustedNetworkV2PolicyReducer {
377
508
  if (acceptedParent.status !== "found") {
378
509
  return this.acceptedAncestryUnavailable(acceptedParent);
379
510
  }
380
- if (candidateParent.status !== "found")
381
- return candidateParent;
511
+ if (candidateParent.status !== "found") {
512
+ return this.candidateAncestryResult(candidateParent);
513
+ }
382
514
  candidateCursor = candidateParent.parent;
383
515
  acceptedCursor = acceptedParent.parent;
384
516
  }
@@ -490,6 +622,9 @@ export class TrustedNetworkV2PolicyReducer {
490
622
  return retention;
491
623
  }
492
624
  async drainPending() {
625
+ if (this.lifecycleController.signal.aborted) {
626
+ return { status: "halted" };
627
+ }
493
628
  let accepted = false;
494
629
  let progress = true;
495
630
  while (progress &&
@@ -502,6 +637,9 @@ export class TrustedNetworkV2PolicyReducer {
502
637
  if (!this.pending.has(pending.snapshot.digestKey))
503
638
  continue;
504
639
  const evaluation = await this.evaluate(pending.snapshot);
640
+ if (this.lifecycleController.signal.aborted) {
641
+ return { status: "halted" };
642
+ }
505
643
  if (evaluation.status === "missing") {
506
644
  pending.missingParentDigest = copyBytes(evaluation.digest);
507
645
  continue;
@@ -525,24 +663,47 @@ export class TrustedNetworkV2PolicyReducer {
525
663
  return accepted ? { status: "accepted" } : undefined;
526
664
  }
527
665
  enqueueAdmission(operation) {
528
- const result = this.admissionTail.then(operation);
666
+ const result = this.admissionTail.then(async () => {
667
+ if (this.lifecycleController.signal.aborted) {
668
+ return this.haltedResult();
669
+ }
670
+ const admission = await operation();
671
+ return this.lifecycleController.signal.aborted
672
+ ? this.haltedResult()
673
+ : admission;
674
+ });
529
675
  this.admissionTail = result.then(() => { }, (_reason) => { });
530
676
  return result;
531
677
  }
532
- ingest(entry) {
533
- return this.enqueueAdmission(() => this.ingestOne(entry));
678
+ ingest(entryBytes) {
679
+ if (this.lifecycleController.signal.aborted) {
680
+ return Promise.resolve(this.haltedResult());
681
+ }
682
+ let capturedEntryBytes;
683
+ try {
684
+ // Capture at the API boundary, before this admission waits behind earlier
685
+ // work. Otherwise a caller could mutate a queued entry before validation.
686
+ capturedEntryBytes = capturePolicySnapshotEntryBytesV2(entryBytes);
687
+ }
688
+ catch (error) {
689
+ const reason = validationMessage(error);
690
+ return this.enqueueAdmission(async () => this.result("rejected", reason));
691
+ }
692
+ return this.enqueueAdmission(() => this.ingestOne(capturedEntryBytes));
534
693
  }
535
694
  retryUnavailable() {
536
695
  return this.enqueueAdmission(() => this.retryUnavailableOne());
537
696
  }
538
- async ingestOne(entry) {
697
+ async ingestOne(entryBytes) {
539
698
  let snapshot;
540
699
  try {
541
- snapshot = await authenticatePolicySnapshotEntryV2(entry, this.descriptor);
700
+ snapshot = await authenticateCapturedPolicySnapshotEntryV2(entryBytes, this.descriptor);
542
701
  }
543
702
  catch (error) {
544
703
  return this.result("rejected", validationMessage(error));
545
704
  }
705
+ if (this.lifecycleController.signal.aborted)
706
+ return this.haltedResult();
546
707
  if (this.fork !== undefined) {
547
708
  this.observeAfterFork(snapshot);
548
709
  return this.result("halted", "Policy reducer is halted by authority equivocation");
@@ -566,6 +727,8 @@ export class TrustedNetworkV2PolicyReducer {
566
727
  return this.result("pending", "Policy snapshot is already pending");
567
728
  }
568
729
  const evaluation = await this.evaluate(snapshot);
730
+ if (this.lifecycleController.signal.aborted)
731
+ return this.haltedResult();
569
732
  if (evaluation.status === "reject") {
570
733
  return this.result("rejected", evaluation.reason);
571
734
  }
@@ -583,7 +746,7 @@ export class TrustedNetworkV2PolicyReducer {
583
746
  if (evaluation.status === "missing") {
584
747
  const pending = this.addPending(snapshot, evaluation.digest);
585
748
  return this.result(pending.retained ? "pending" : "capacity", pending.retained
586
- ? "Policy parent is missing"
749
+ ? (evaluation.reason ?? "Policy parent is missing")
587
750
  : "Policy pending capacity did not retain this candidate", pending.evictedPolicyDigests);
588
751
  }
589
752
  this.project(snapshot);
@@ -591,6 +754,8 @@ export class TrustedNetworkV2PolicyReducer {
591
754
  this.result("accepted"));
592
755
  }
593
756
  async retryUnavailableOne() {
757
+ if (this.lifecycleController.signal.aborted)
758
+ return this.haltedResult();
594
759
  if (this.fork !== undefined) {
595
760
  return this.result("halted", "Policy reducer is halted by authority equivocation");
596
761
  }
@@ -603,6 +768,8 @@ export class TrustedNetworkV2PolicyReducer {
603
768
  return this.result("capacity", "Unavailable comparison candidate is not retained; re-ingest it before retrying");
604
769
  }
605
770
  const evaluation = await this.evaluate(pending.snapshot);
771
+ if (this.lifecycleController.signal.aborted)
772
+ return this.haltedResult();
606
773
  if (evaluation.status === "unavailable") {
607
774
  pending.missingParentDigest = copyBytes(evaluation.digest);
608
775
  this.unavailable = {
@@ -618,7 +785,7 @@ export class TrustedNetworkV2PolicyReducer {
618
785
  if (evaluation.status === "missing") {
619
786
  pending.missingParentDigest = copyBytes(evaluation.digest);
620
787
  status = "pending";
621
- reason = "Policy parent is missing";
788
+ reason = evaluation.reason ?? "Policy parent is missing";
622
789
  }
623
790
  else {
624
791
  this.pending.delete(pending.snapshot.digestKey);