@serve.zone/interfaces 31.7.0 → 31.9.0

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.
Files changed (59) hide show
  1. package/changelog.md +77 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/data/clusterruntime.d.ts +39 -0
  4. package/dist_ts/data/clusterruntime.js +11 -0
  5. package/dist_ts/data/index.d.ts +5 -0
  6. package/dist_ts/data/index.js +6 -1
  7. package/dist_ts/data/networkaddressplan.d.ts +78 -0
  8. package/dist_ts/data/networkaddressplan.js +114 -0
  9. package/dist_ts/data/networkauthority.js +5 -17
  10. package/dist_ts/data/networkcredential.d.ts +10 -4
  11. package/dist_ts/data/networkcredential.js +14 -8
  12. package/dist_ts/data/networkdelivery.d.ts +21 -0
  13. package/dist_ts/data/networkdelivery.js +35 -7
  14. package/dist_ts/data/networklease.d.ts +110 -0
  15. package/dist_ts/data/networklease.js +159 -0
  16. package/dist_ts/data/networknode.d.ts +37 -0
  17. package/dist_ts/data/networknode.js +6 -0
  18. package/dist_ts/data/networkprojection.d.ts +10 -5
  19. package/dist_ts/data/networkprojection.js +15 -22
  20. package/dist_ts/data/networksigning.js +5 -4
  21. package/dist_ts/data/runtimeconfig.js +14 -77
  22. package/dist_ts/data/serviceruntime.d.ts +128 -0
  23. package/dist_ts/data/serviceruntime.js +92 -0
  24. package/dist_ts/private/networkcontract.d.ts +20 -0
  25. package/dist_ts/private/networkcontract.js +45 -1
  26. package/dist_ts/private/producercontract.d.ts +11 -0
  27. package/dist_ts/private/producercontract.js +21 -0
  28. package/dist_ts/private/runtimepolicy.d.ts +31 -0
  29. package/dist_ts/private/runtimepolicy.js +90 -0
  30. package/dist_ts/requests/cluster.d.ts +53 -0
  31. package/dist_ts/requests/cluster.js +1 -1
  32. package/dist_ts/requests/network.d.ts +57 -1
  33. package/dist_ts/requests/runtimesession.d.ts +14 -0
  34. package/dist_ts/requests/service.d.ts +60 -0
  35. package/dist_ts/runtime.networkprojection.d.ts +14 -0
  36. package/dist_ts/runtime.networkprojection.js +68 -19
  37. package/package.json +2 -2
  38. package/readme.md +251 -3
  39. package/ts/00_commitinfo_data.ts +1 -1
  40. package/ts/data/clusterruntime.ts +45 -0
  41. package/ts/data/index.ts +5 -0
  42. package/ts/data/networkaddressplan.ts +175 -0
  43. package/ts/data/networkauthority.ts +4 -16
  44. package/ts/data/networkcredential.ts +19 -9
  45. package/ts/data/networkdelivery.ts +49 -6
  46. package/ts/data/networklease.ts +229 -0
  47. package/ts/data/networknode.ts +37 -0
  48. package/ts/data/networkprojection.ts +28 -26
  49. package/ts/data/networksigning.ts +5 -4
  50. package/ts/data/runtimeconfig.ts +17 -76
  51. package/ts/data/serviceruntime.ts +208 -0
  52. package/ts/private/networkcontract.ts +48 -0
  53. package/ts/private/producercontract.ts +19 -0
  54. package/ts/private/runtimepolicy.ts +105 -0
  55. package/ts/requests/cluster.ts +65 -0
  56. package/ts/requests/network.ts +50 -1
  57. package/ts/requests/runtimesession.ts +17 -0
  58. package/ts/requests/service.ts +81 -0
  59. package/ts/runtime.networkprojection.ts +87 -20
@@ -1,8 +1,9 @@
1
1
  import type { IRuntimeAuthorityReferenceV1 } from './runtimeassignment.js';
2
2
  import { snapshotRuntimeSessionBindingV1, type IRuntimeSessionBindingV1 } from './runtimesession.js';
3
3
  import { validateRuntimeNetworkProjectionV1, type IRuntimeNetworkProjectionV1 } from './networkprojection.js';
4
- import { captureNetwork, failNetwork, networkCounter, networkEqual, networkId, networkIpv4,
5
- networkRecord, networkReference, networkReferenceOf, networkSafe, requireNetwork } from '../private/networkcontract.js';
4
+ import { snapshotRuntimeNetworkDnsLeaseRenewal, validateRuntimeNetworkDnsLeaseRenewal } from './networklease.js';
5
+ import { captureNetwork, failNetwork, networkCounter, networkEffectiveDnsWindow, networkEqual, networkId, networkIpv4,
6
+ networkRecord, networkReference, networkReferenceOf, networkSafe, networkSameScope, requireNetwork } from '../private/networkcontract.js';
6
7
 
7
8
  export interface IGetRuntimeManagedVpnCredentialRequestV1 {
8
9
  session: IRuntimeSessionBindingV1;
@@ -19,7 +20,10 @@ export interface IGetRuntimeManagedVpnCredentialResponseV1 extends IGetRuntimeMa
19
20
  serverPublicKey: string;
20
21
  clientPublicKey: string;
21
22
  clientPrivateKey: string;
22
- /** Unix milliseconds; cannot outlive the referenced projection's DNS lease. */
23
+ /**
24
+ * Unix milliseconds; cannot outlive the referenced projection's effective DNS window: its
25
+ * admitted lease renewal's, else its own.
26
+ */
23
27
  expiresAt: number;
24
28
  }
25
29
 
@@ -50,8 +54,7 @@ export const snapshotGetRuntimeManagedVpnCredentialResponseV1 = (value: unknown)
50
54
  const bindRequest = (request: IGetRuntimeManagedVpnCredentialRequestV1,
51
55
  projection: IRuntimeNetworkProjectionV1, trusted: IRuntimeSessionBindingV1): void => {
52
56
  requireNetwork(networkEqual(request.session, trusted) && networkEqual(request.projection, networkReferenceOf(projection))
53
- && networkEqual(projection.controller, trusted.controller) && projection.nodeId === trusted.nodeId
54
- && projection.runtimeNamespace === trusted.runtimeNamespace && projection.handoff);
57
+ && networkSameScope(projection, trusted) && projection.handoff);
55
58
  };
56
59
 
57
60
  /** Content binding only. The caller must resolve the exact current projection
@@ -68,21 +71,28 @@ export const bindGetRuntimeManagedVpnCredentialRequestV1 = async (value: unknown
68
71
  };
69
72
 
70
73
  /** Bind the secret response to the original request and exact selected router
71
- * UDP endpoint. Time is caller-qualified; this helper does not sample a clock,
72
- * authenticate a transport, admit policy or prove a running managed connection. */
74
+ * UDP endpoint, inside the projection's effective DNS window. renewalValue is the
75
+ * bare lease renewal the caller already verified and admitted for this exact
76
+ * projection, or null; this helper verifies no signature. Time is caller-qualified;
77
+ * this helper does not sample a clock, authenticate a transport, admit policy or
78
+ * prove a running managed connection. */
73
79
  export const bindGetRuntimeManagedVpnCredentialResponseV1 = async (value: unknown, requestValue: unknown,
74
- projectionValue: unknown, trustedSessionValue: unknown, nowValue: unknown): Promise<IGetRuntimeManagedVpnCredentialResponseV1> => {
80
+ projectionValue: unknown, trustedSessionValue: unknown, nowValue: unknown,
81
+ renewalValue: unknown | null = null): Promise<IGetRuntimeManagedVpnCredentialResponseV1> => {
75
82
  try {
76
83
  const response = snapshotGetRuntimeManagedVpnCredentialResponseV1(value);
77
84
  const request = snapshotGetRuntimeManagedVpnCredentialRequestV1(requestValue);
78
85
  const trusted = snapshotRuntimeSessionBindingV1(trustedSessionValue), now = networkCounter(nowValue);
86
+ const renewalInput = renewalValue === null ? null : snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue);
79
87
  const projection = await validateRuntimeNetworkProjectionV1(projectionValue);
88
+ const renewal = renewalInput === null ? null : await validateRuntimeNetworkDnsLeaseRenewal(renewalInput);
89
+ const window = networkEffectiveDnsWindow(projection, renewal);
80
90
  bindRequest(request, projection, trusted);
81
91
  requireNetwork(networkEqual(response.session, request.session) && networkEqual(response.projection, request.projection));
82
92
  requireNetwork(projection.router?.platformEndpointIds.includes(response.relay.id));
83
93
  const endpoint = projection.protectedAuthority.platformEndpoints.find(item => item.id === response.relay.id);
84
94
  requireNetwork(endpoint && endpoint.protocol === 'udp' && endpoint.address === response.relay.address && endpoint.port === response.relay.port);
85
- requireNetwork(projection.dns.notBefore <= now && now < response.expiresAt && response.expiresAt <= projection.dns.expiresAt);
95
+ requireNetwork(window.notBefore <= now && now < response.expiresAt && response.expiresAt <= window.expiresAt);
86
96
  return response;
87
97
  } catch { return failNetwork(); }
88
98
  };
@@ -3,7 +3,11 @@ import type { IRuntimeSessionBindingV1 } from './runtimesession.js';
3
3
  import { snapshotRuntimeSessionBindingV1 } from './runtimesession.js';
4
4
  import type { IRuntimeNetworkSigningAuthorityV1, ISignedRuntimeNetworkProjectionV1 } from './networksigning.js';
5
5
  import { snapshotRuntimeNetworkSigningAuthorityV1, snapshotSignedRuntimeNetworkProjectionV1 } from './networksigning.js';
6
- import { captureNetwork, networkEqual, networkRecord, networkReference, networkReferenceOf, networkSafe, requireNetwork } from '../private/networkcontract.js';
6
+ import type { IRuntimeNetworkDnsLeaseRenewalReference, ISignedRuntimeNetworkDnsLeaseRenewal } from './networklease.js';
7
+ import { getRuntimeNetworkDnsLeaseRenewalReference, snapshotRuntimeNetworkDnsLeaseRenewal,
8
+ snapshotRuntimeNetworkDnsLeaseRenewalReference, snapshotSignedRuntimeNetworkDnsLeaseRenewal } from './networklease.js';
9
+ import { captureNetwork, networkEqual, networkRecord, networkReference, networkReferenceOf, networkSafe, networkSameScope,
10
+ requireNetwork } from '../private/networkcontract.js';
7
11
 
8
12
  export interface IApplyRuntimeNetworkSigningAuthorityRequestV1 {
9
13
  session: IRuntimeSessionBindingV1;
@@ -18,6 +22,19 @@ export interface IApplyRuntimeNetworkAuthorityResponseV1 {
18
22
  status: 'accepted' | 'replay';
19
23
  authority: IRuntimeAuthorityReferenceV1;
20
24
  }
25
+ export interface IApplyRuntimeNetworkDnsLeaseRenewalRequest {
26
+ session: IRuntimeSessionBindingV1;
27
+ signed: ISignedRuntimeNetworkDnsLeaseRenewal;
28
+ }
29
+ /** Durable admission only. It does not assert that the node converted the window or serves DNS. */
30
+ export interface IApplyRuntimeNetworkDnsLeaseRenewalResponse {
31
+ status: 'accepted' | 'replay';
32
+ renewal: IRuntimeNetworkDnsLeaseRenewalReference;
33
+ }
34
+
35
+ const admissionStatus = (value: unknown): 'accepted' | 'replay' => {
36
+ requireNetwork(value === 'accepted' || value === 'replay'); return value;
37
+ };
21
38
 
22
39
  export const snapshotApplyRuntimeNetworkSigningAuthorityRequestV1 = (value: unknown): IApplyRuntimeNetworkSigningAuthorityRequestV1 => networkSafe(() => {
23
40
  const item = networkRecord(captureNetwork(value), ['session', 'authority']);
@@ -29,8 +46,16 @@ export const snapshotApplyRuntimeNetworkProjectionRequestV1 = (value: unknown):
29
46
  });
30
47
  export const snapshotApplyRuntimeNetworkAuthorityResponseV1 = (value: unknown): IApplyRuntimeNetworkAuthorityResponseV1 => networkSafe(() => {
31
48
  const item = networkRecord(captureNetwork(value), ['status', 'authority']);
32
- requireNetwork(item.status === 'accepted' || item.status === 'replay');
33
- return { status: item.status, authority: networkReference(item.authority) };
49
+ return { status: admissionStatus(item.status), authority: networkReference(item.authority) };
50
+ });
51
+
52
+ export const snapshotApplyRuntimeNetworkDnsLeaseRenewalRequest = (value: unknown): IApplyRuntimeNetworkDnsLeaseRenewalRequest => networkSafe(() => {
53
+ const item = networkRecord(captureNetwork(value), ['session', 'signed']);
54
+ return { session: snapshotRuntimeSessionBindingV1(item.session), signed: snapshotSignedRuntimeNetworkDnsLeaseRenewal(item.signed) };
55
+ });
56
+ export const snapshotApplyRuntimeNetworkDnsLeaseRenewalResponse = (value: unknown): IApplyRuntimeNetworkDnsLeaseRenewalResponse => networkSafe(() => {
57
+ const item = networkRecord(captureNetwork(value), ['status', 'renewal']);
58
+ return { status: admissionStatus(item.status), renewal: snapshotRuntimeNetworkDnsLeaseRenewalReference(item.renewal) };
34
59
  });
35
60
 
36
61
  /** trustedSessionValue must come from the receiving CURRENT physical connection. */
@@ -43,9 +68,7 @@ export const bindApplyRuntimeNetworkSigningAuthorityRequestV1 = (value: unknown,
43
68
  /** Content binding only. Verify signature with stored trust and atomically fence admission. */
44
69
  export const bindApplyRuntimeNetworkProjectionRequestV1 = (value: unknown, trustedSessionValue: unknown): IApplyRuntimeNetworkProjectionRequestV1 => networkSafe(() => {
45
70
  const request = snapshotApplyRuntimeNetworkProjectionRequestV1(value), session = snapshotRuntimeSessionBindingV1(trustedSessionValue);
46
- const projection = request.signed.projection;
47
- requireNetwork(networkEqual(request.session, session) && networkEqual(projection.controller, session.controller)
48
- && projection.nodeId === session.nodeId && projection.runtimeNamespace === session.runtimeNamespace);
71
+ requireNetwork(networkEqual(request.session, session) && networkSameScope(request.signed.projection, session));
49
72
  return request;
50
73
  });
51
74
 
@@ -56,3 +79,23 @@ export const bindApplyRuntimeNetworkAuthorityResponseV1 = (value: unknown, sentR
56
79
  requireNetwork(networkEqual(response.authority, networkReferenceOf(expected)));
57
80
  return response;
58
81
  });
82
+
83
+ /**
84
+ * Content binding only; trustedSessionValue must come from the receiving CURRENT physical
85
+ * connection. A relay pushes a held renewal under the binding the node obtained itself, so this
86
+ * comparison is what makes the push admissible, never who sent it. Verify the signature with
87
+ * stored trust, then admit and fence atomically.
88
+ */
89
+ export const bindApplyRuntimeNetworkDnsLeaseRenewalRequest = (value: unknown, trustedSessionValue: unknown): IApplyRuntimeNetworkDnsLeaseRenewalRequest => networkSafe(() => {
90
+ const request = snapshotApplyRuntimeNetworkDnsLeaseRenewalRequest(value), session = snapshotRuntimeSessionBindingV1(trustedSessionValue);
91
+ requireNetwork(networkEqual(request.session, session) && networkSameScope(request.signed.renewal, session));
92
+ return request;
93
+ });
94
+
95
+ /** Compare the ACK with the exact SENT renewal, never with a response-derived value. */
96
+ export const bindApplyRuntimeNetworkDnsLeaseRenewalResponse = (value: unknown, sentRenewalValue: unknown): IApplyRuntimeNetworkDnsLeaseRenewalResponse => networkSafe(() => {
97
+ const response = snapshotApplyRuntimeNetworkDnsLeaseRenewalResponse(value);
98
+ const sent = getRuntimeNetworkDnsLeaseRenewalReference(snapshotRuntimeNetworkDnsLeaseRenewal(sentRenewalValue));
99
+ requireNetwork(networkEqual(response.renewal, sent));
100
+ return response;
101
+ });
@@ -0,0 +1,229 @@
1
+ import type { TSha256Digest } from './immutableimage.js';
2
+ import type { IRuntimeAuthorityReferenceV1 } from './runtimeassignment.js';
3
+ import type { IRuntimeNetworkDnsWindow, IRuntimeNetworkNodeScopeV1 } from './networkprojection.js';
4
+ import { snapshotRuntimeNetworkNodeScopeV1, snapshotRuntimeNetworkProjectionV1, validateRuntimeNetworkProjectionV1 } from './networkprojection.js';
5
+ import { captureNetwork, failNetwork, networkCounter, networkDigest, networkDnsWindow, networkEffectiveDnsWindow, networkEqual,
6
+ networkHash, networkId, networkList, networkMaximumDnsLeaseMs, networkRecord, networkReference, networkReferenceOf,
7
+ networkRenewsProjection, networkSafe, networkSameScope, networkScope, networkSignature, requireNetwork } from '../private/networkcontract.js';
8
+
9
+ /**
10
+ * A fresh DNS window for one exact admitted projection, and nothing else.
11
+ *
12
+ * A projection's window is signed content, so only a newly signed statement can extend it. A
13
+ * re-issued projection would do that too, but a receiver treats every new projection as new
14
+ * network state and applies it again; a renewal leaves the applied state alone. It names the
15
+ * projection by exact reference, so it never joins the projection chain and stops being
16
+ * admissible the moment a successor projection is admitted.
17
+ */
18
+ export interface IRuntimeNetworkDnsLeaseRenewal extends IRuntimeNetworkNodeScopeV1 {
19
+ projection: IRuntimeAuthorityReferenceV1;
20
+ /** Orders the renewals of one projection. A receiver may skip sequences, never go back. */
21
+ sequence: number;
22
+ /**
23
+ * `issuedAt` is the start of the window's slot, not the signing instant: a controller signs a
24
+ * run of future windows ahead of time, and the fifteen-minute rule is measured from here.
25
+ */
26
+ dns: IRuntimeNetworkDnsWindow;
27
+ digest: TSha256Digest;
28
+ }
29
+
30
+ /** Names one renewal without its window: what a receiver records and what an ACK repeats. */
31
+ export interface IRuntimeNetworkDnsLeaseRenewalReference {
32
+ projection: IRuntimeAuthorityReferenceV1;
33
+ sequence: number;
34
+ digest: TSha256Digest;
35
+ }
36
+
37
+ export interface ISignedRuntimeNetworkDnsLeaseRenewal {
38
+ /** The signing authority revision that also signs this controller's projections. */
39
+ authority: IRuntimeAuthorityReferenceV1;
40
+ renewal: IRuntimeNetworkDnsLeaseRenewal;
41
+ /** Ed25519 signature of the complete domain-separated envelope, lowercase hex. */
42
+ signature: string;
43
+ }
44
+
45
+ /**
46
+ * The renewals a cluster relay holds for one node it carries, replacing what it held before.
47
+ *
48
+ * The relay has no signing trust and verifies no signature: it holds signed statements and hands
49
+ * them to the node, which verifies each one against its own pinned authority.
50
+ */
51
+ export interface IHoldRuntimeNetworkDnsLeaseRenewalsRequest {
52
+ /** The relay's own socket names the cluster; one relay carries every node of it, so the node is named here. */
53
+ nodeId: string;
54
+ projection: IRuntimeAuthorityReferenceV1;
55
+ /** Ascending by sequence. Empty holds nothing for this node. */
56
+ renewals: ISignedRuntimeNetworkDnsLeaseRenewal[];
57
+ }
58
+
59
+ export interface IHoldRuntimeNetworkDnsLeaseRenewalsResponse {
60
+ held: number;
61
+ }
62
+
63
+ export const runtimeNetworkDnsLeaseRenewalContract = Object.freeze({
64
+ renewalDigestDomain: 'serve.zone/runtime-network-dns-lease-renewal',
65
+ signatureDomain: 'serve.zone/runtime-network-dns-lease-renewal-signature',
66
+ maximumDnsLeaseMs: networkMaximumDnsLeaseMs,
67
+ maximumEscrowedRenewals: 32,
68
+ /** From the first held window's `issuedAt` to the last one's `expiresAt`. */
69
+ maximumEscrowHorizonMs: 14_400_000,
70
+ binding: 'exact-projection-reference',
71
+ supersession: 'a-successor-projection-refuses-every-renewal-of-its-predecessor',
72
+ sequence: 'strictly-increasing-per-projection-skipping-allowed',
73
+ effect: 'dns-window-only-never-applied-network-state',
74
+ escrowRelease: 'only-the-current-window-and-only-while-the-holder-has-no-controller-session',
75
+ revocationLatency: 'a-detached-cluster-keeps-dns-authority-until-its-last-held-window-ends',
76
+ } as const);
77
+
78
+ export const snapshotRuntimeNetworkDnsLeaseRenewal = (value: unknown): IRuntimeNetworkDnsLeaseRenewal => networkSafe(() => {
79
+ const item = networkRecord(captureNetwork(value), ['controller', 'nodeId', 'runtimeNamespace', 'projection', 'sequence', 'dns', 'digest']);
80
+ return { ...networkScope(item), projection: networkReference(item.projection), sequence: networkCounter(item.sequence),
81
+ dns: networkDnsWindow(item.dns), digest: networkDigest(item.digest) };
82
+ });
83
+
84
+ export const snapshotRuntimeNetworkDnsLeaseRenewalReference = (value: unknown): IRuntimeNetworkDnsLeaseRenewalReference => networkSafe(() => {
85
+ const item = networkRecord(captureNetwork(value), ['projection', 'sequence', 'digest']);
86
+ return { projection: networkReference(item.projection), sequence: networkCounter(item.sequence), digest: networkDigest(item.digest) };
87
+ });
88
+
89
+ export const getRuntimeNetworkDnsLeaseRenewalReference = (value: IRuntimeNetworkDnsLeaseRenewal): IRuntimeNetworkDnsLeaseRenewalReference =>
90
+ ({ projection: networkReferenceOf(value.projection), sequence: value.sequence, digest: value.digest });
91
+
92
+ export const snapshotSignedRuntimeNetworkDnsLeaseRenewal = (value: unknown): ISignedRuntimeNetworkDnsLeaseRenewal => networkSafe(() => {
93
+ const item = networkRecord(captureNetwork(value), ['authority', 'renewal', 'signature']);
94
+ return { authority: networkReference(item.authority), renewal: snapshotRuntimeNetworkDnsLeaseRenewal(item.renewal),
95
+ signature: networkSignature(item.signature) };
96
+ });
97
+
98
+ export const computeRuntimeNetworkDnsLeaseRenewalDigest = (value: IRuntimeNetworkDnsLeaseRenewal): Promise<TSha256Digest> =>
99
+ networkHash(runtimeNetworkDnsLeaseRenewalContract.renewalDigestDomain, snapshotRuntimeNetworkDnsLeaseRenewal(value));
100
+
101
+ /** Structure and digest only. A digest authenticates nothing: verify the signature with stored trust. */
102
+ export const validateRuntimeNetworkDnsLeaseRenewal = async (value: unknown): Promise<IRuntimeNetworkDnsLeaseRenewal> => {
103
+ try {
104
+ const renewal = snapshotRuntimeNetworkDnsLeaseRenewal(value);
105
+ requireNetwork(renewal.digest === await computeRuntimeNetworkDnsLeaseRenewalDigest(renewal));
106
+ return renewal;
107
+ } catch { return failNetwork(); }
108
+ };
109
+
110
+ /**
111
+ * A later window never moves its slot back and always ends later. Windows need not touch: a
112
+ * receiver that missed renewals while its lease lapsed must still be able to admit the next one.
113
+ */
114
+ const extendsWindow = (next: IRuntimeNetworkDnsWindow, baseline: IRuntimeNetworkDnsWindow): boolean =>
115
+ next.issuedAt >= baseline.issuedAt && next.expiresAt > baseline.expiresAt;
116
+
117
+ /** A receiver admits only a window it can use at once: its slot has opened and it has not ended. */
118
+ const isCurrentWindow = (window: IRuntimeNetworkDnsWindow, now: number): boolean =>
119
+ window.notBefore <= now && now < window.expiresAt;
120
+
121
+ /**
122
+ * The DNS window a node may use under a projection: its admitted renewal's, else the projection's
123
+ * own. Controller and node derive every window-bounded fact, such as a VPN credential's expiry,
124
+ * from this one rule. renewalValue is the bare renewal `admitRuntimeNetworkDnsLeaseRenewal`
125
+ * admitted, or null; Cloudly passes `signed.renewal`. Both inputs are the caller's admitted state,
126
+ * so this checks structure, digests and that the renewal names exactly this projection; it
127
+ * verifies no signature and samples no clock.
128
+ */
129
+ export const getRuntimeNetworkEffectiveDnsWindow = async (
130
+ projectionValue: unknown, renewalValue: unknown | null,
131
+ ): Promise<IRuntimeNetworkDnsWindow> => {
132
+ try {
133
+ const projectionInput = snapshotRuntimeNetworkProjectionV1(projectionValue);
134
+ const renewalInput = renewalValue === null ? null : snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue);
135
+ const projection = await validateRuntimeNetworkProjectionV1(projectionInput);
136
+ const renewal = renewalInput === null ? null : await validateRuntimeNetworkDnsLeaseRenewal(renewalInput);
137
+ return networkEffectiveDnsWindow(projection, renewal);
138
+ } catch { return failNetwork(); }
139
+ };
140
+
141
+ /**
142
+ * Pure admission decision for a renewal whose signature the caller has already verified.
143
+ *
144
+ * currentProjectionValue is the receiver's own admitted projection and previousValue the last
145
+ * renewal it admitted for that exact projection, or null when there is none; neither is ever
146
+ * taken from the incoming message. Without a previous renewal the projection's own window is the
147
+ * baseline. nowValue is the caller's qualified clock: a renewal is admitted only inside its own
148
+ * window, because storing a window that has not opened would displace the coverage the receiver
149
+ * has now. A sender therefore delivers a window once its slot has opened and never before, and
150
+ * simply delivers it again if it was refused. Re-delivering the renewal already admitted is
151
+ * answered `replay` whatever the time, so re-delivery stays idempotent. The caller commits the
152
+ * result atomically with its trust fence and discards the stored renewal in the transaction that
153
+ * admits a successor projection.
154
+ */
155
+ export const admitRuntimeNetworkDnsLeaseRenewal = async (
156
+ value: unknown, currentProjectionValue: unknown, previousValue: unknown | null, trustedScopeValue: unknown, nowValue: unknown,
157
+ ): Promise<{ status: 'accepted' | 'replay'; renewal: IRuntimeNetworkDnsLeaseRenewal }> => {
158
+ try {
159
+ // Snapshot ALL caller-owned inputs before the first asynchronous operation.
160
+ const incoming = snapshotRuntimeNetworkDnsLeaseRenewal(value);
161
+ const projectionInput = snapshotRuntimeNetworkProjectionV1(currentProjectionValue);
162
+ const previousInput = previousValue === null ? null : snapshotRuntimeNetworkDnsLeaseRenewal(previousValue);
163
+ const trusted = snapshotRuntimeNetworkNodeScopeV1(trustedScopeValue);
164
+ const now = networkCounter(nowValue);
165
+ const renewal = await validateRuntimeNetworkDnsLeaseRenewal(incoming);
166
+ const projection = await validateRuntimeNetworkProjectionV1(projectionInput);
167
+ requireNetwork(networkSameScope(projection, trusted) && networkRenewsProjection(renewal, projection));
168
+ const previous = previousInput === null ? null : await validateRuntimeNetworkDnsLeaseRenewal(previousInput);
169
+ if (previous !== null) {
170
+ requireNetwork(networkRenewsProjection(previous, projection));
171
+ if (renewal.sequence === previous.sequence) {
172
+ requireNetwork(networkEqual(renewal, previous)); return { status: 'replay', renewal };
173
+ }
174
+ requireNetwork(renewal.sequence > previous.sequence);
175
+ }
176
+ requireNetwork(isCurrentWindow(renewal.dns, now));
177
+ requireNetwork(extendsWindow(renewal.dns, previous === null ? projection.dns : previous.dns));
178
+ return { status: 'accepted', renewal };
179
+ } catch { return failNetwork(); }
180
+ };
181
+
182
+ /** Consecutive held renewals: same signer and scope, a higher sequence, a window that extends the one before. */
183
+ const followsInRun = (next: ISignedRuntimeNetworkDnsLeaseRenewal, before: ISignedRuntimeNetworkDnsLeaseRenewal): boolean =>
184
+ networkEqual(next.authority, before.authority) && networkSameScope(next.renewal, before.renewal)
185
+ && next.renewal.sequence > before.renewal.sequence && extendsWindow(next.renewal.dns, before.renewal.dns);
186
+
187
+ const heldHorizonMs = (renewals: ISignedRuntimeNetworkDnsLeaseRenewal[]): number => {
188
+ const first = renewals.at(0), last = renewals.at(-1);
189
+ return first === undefined || last === undefined ? 0 : last.renewal.dns.expiresAt - first.renewal.dns.issuedAt;
190
+ };
191
+
192
+ /**
193
+ * A held run is what one node could admit in order: one node, one projection, one authority,
194
+ * ascending sequences, each window extending the one before, inside both escrow maxima.
195
+ */
196
+ export const snapshotHoldRuntimeNetworkDnsLeaseRenewalsRequest = (value: unknown): IHoldRuntimeNetworkDnsLeaseRenewalsRequest => networkSafe(() => {
197
+ const item = networkRecord(captureNetwork(value), ['nodeId', 'projection', 'renewals']);
198
+ const contract = runtimeNetworkDnsLeaseRenewalContract;
199
+ const request: IHoldRuntimeNetworkDnsLeaseRenewalsRequest = { nodeId: networkId(item.nodeId), projection: networkReference(item.projection),
200
+ renewals: networkList(item.renewals, contract.maximumEscrowedRenewals, snapshotSignedRuntimeNetworkDnsLeaseRenewal) };
201
+ request.renewals.forEach((signed, index) => requireNetwork(signed.renewal.nodeId === request.nodeId
202
+ && networkEqual(signed.renewal.projection, request.projection)
203
+ && (index === 0 || followsInRun(signed, request.renewals[index - 1]))));
204
+ requireNetwork(heldHorizonMs(request.renewals) <= contract.maximumEscrowHorizonMs);
205
+ return request;
206
+ });
207
+
208
+ /** The run's rules plus every renewal's digest. It verifies no signature: a relay holds no trust. */
209
+ export const validateHoldRuntimeNetworkDnsLeaseRenewalsRequest = async (value: unknown): Promise<IHoldRuntimeNetworkDnsLeaseRenewalsRequest> => {
210
+ try {
211
+ const request = snapshotHoldRuntimeNetworkDnsLeaseRenewalsRequest(value);
212
+ for (const signed of request.renewals) await validateRuntimeNetworkDnsLeaseRenewal(signed.renewal);
213
+ return request;
214
+ } catch { return failNetwork(); }
215
+ };
216
+
217
+ export const snapshotHoldRuntimeNetworkDnsLeaseRenewalsResponse = (value: unknown): IHoldRuntimeNetworkDnsLeaseRenewalsResponse => networkSafe(() => {
218
+ const item = networkRecord(captureNetwork(value), ['held']);
219
+ requireNetwork(typeof item.held === 'number' && Number.isSafeInteger(item.held) && item.held >= 0
220
+ && item.held <= runtimeNetworkDnsLeaseRenewalContract.maximumEscrowedRenewals);
221
+ return { held: item.held };
222
+ });
223
+
224
+ /** Compare the answer with the exact SENT run: a relay that held fewer did not take the run over. */
225
+ export const bindHoldRuntimeNetworkDnsLeaseRenewalsResponse = (value: unknown, sentRequestValue: unknown): IHoldRuntimeNetworkDnsLeaseRenewalsResponse => networkSafe(() => {
226
+ const response = snapshotHoldRuntimeNetworkDnsLeaseRenewalsResponse(value);
227
+ requireNetwork(response.held === snapshotHoldRuntimeNetworkDnsLeaseRenewalsRequest(sentRequestValue).renewals.length);
228
+ return response;
229
+ });
@@ -0,0 +1,37 @@
1
+ import type { IRuntimeNetworkProtectedAuthorityV1 } from './networkauthority.js';
2
+ import { captureNetwork, networkCounter, networkId, networkRecord, networkSafe } from '../private/networkcontract.js';
3
+
4
+ type TEgressOwner = IRuntimeNetworkProtectedAuthorityV1['egressAuthorities'][number];
5
+
6
+ /**
7
+ * One unmet condition of a node's runtime network readiness, in the order the node producer meets
8
+ * them: the node joins the protected authority's egress, every egress owner reports current
9
+ * protection, a handoff is reserved, the router selection carries it, the node's acknowledged
10
+ * projection carries it, and the node is a managed VPN member. `protection-receipts-pending` names
11
+ * the egress owners whose current receipts are still missing.
12
+ */
13
+ export type TRuntimeNetworkNodeReadinessGap =
14
+ | { condition: 'egress-pending' | 'handoff-pending' | 'selection-pending' | 'projection-pending' | 'vpn-member-pending' }
15
+ | { condition: 'protection-receipts-pending'; owners: [TEgressOwner, ...TEgressOwner[]] };
16
+
17
+ /** A node is ready exactly when nothing is missing. */
18
+ export type IRuntimeNetworkNodeReadiness = { nodeId: string } & (
19
+ | { ready: true; missing: [] }
20
+ | { ready: false; missing: [TRuntimeNetworkNodeReadinessGap, ...TRuntimeNetworkNodeReadinessGap[]] }
21
+ );
22
+
23
+ /**
24
+ * The router incarnation of one node. A handoff's id and router incarnation id derive from it, so
25
+ * advancing it is the only way to give a node's router a fresh handoff.
26
+ */
27
+ export interface IRuntimeRouterIncarnation {
28
+ /** The node id. */
29
+ id: string;
30
+ /** Starts at 1 with the node's first handoff; every reincarnation advances it by one. */
31
+ incarnation: number;
32
+ }
33
+
34
+ export const snapshotRuntimeRouterIncarnation = (value: unknown): IRuntimeRouterIncarnation => networkSafe(() => {
35
+ const item = networkRecord(captureNetwork(value), ['id', 'incarnation']);
36
+ return { id: networkId(item.id), incarnation: networkCounter(item.incarnation) };
37
+ });
@@ -9,10 +9,10 @@ import { routerPacketGrantsOf, snapshotRouterProjection,
9
9
  type IRuntimeNetworkRouterPacketGrantV1, type IRuntimeNetworkRouterProjectionV1 } from './networkrouter.js';
10
10
  import { buildPrivateNetworkFqdnV1, composeServicePrivateNetworkDnsPolicyV1,
11
11
  snapshotPrivateNetworkV1, snapshotServicePrivateNetworksV1 } from './privatenetwork.js';
12
- import { captureNetwork, failNetwork, networkCanonical, networkContains, networkController, networkCounter,
13
- networkDigest, networkEqual, networkHash, networkId, networkIpv4, networkIpv4Number, networkList,
14
- networkOverlaps, networkPrefixBounds, networkRecord, networkReference, networkReferenceOf, networkRevision,
15
- networkSafe, networkSorted, requireNetwork } from '../private/networkcontract.js';
12
+ import { captureNetwork, failNetwork, networkCanonical, networkContains, networkCounter, networkDigest,
13
+ networkDnsWindow, networkEqual, networkHash, networkId, networkIpv4, networkIpv4Number, networkList, networkMaximumDnsLeaseMs,
14
+ networkOverlaps, networkPrefixBounds, networkRecord, networkReference, networkReferenceOf, networkRevision, networkSafe,
15
+ networkSameScope, networkScope, networkSorted, requireNetwork } from '../private/networkcontract.js';
16
16
 
17
17
  export interface IRuntimeNetworkNodeScopeV1 {
18
18
  controller: IRuntimeControllerV1;
@@ -20,6 +20,16 @@ export interface IRuntimeNetworkNodeScopeV1 {
20
20
  runtimeNamespace: string;
21
21
  }
22
22
 
23
+ /**
24
+ * One signed DNS window, on a projection or on a renewal of it: the start of the slot it was
25
+ * issued for, the instant a receiver may use it, and the instant it stops being usable.
26
+ */
27
+ export interface IRuntimeNetworkDnsWindow {
28
+ issuedAt: number;
29
+ notBefore: number;
30
+ expiresAt: number;
31
+ }
32
+
23
33
  /** Immutable per-attempt address material. Membership and projection revisions are independent. */
24
34
  export interface IRuntimeNetworkWorkloadLeaseV1 extends IRuntimeNetworkNodeScopeV1 {
25
35
  schemaVersion: 1;
@@ -143,7 +153,7 @@ export interface IRuntimeNetworkProjectionV1 extends IRuntimeNetworkNodeScopeV1
143
153
  networks: IPrivateNetworkV1[];
144
154
  memberships: IServicePrivateNetworksV1[];
145
155
  endpoints: IRuntimeNetworkEndpointV1[];
146
- dns: { issuedAt: number; notBefore: number; expiresAt: number };
156
+ dns: IRuntimeNetworkDnsWindow;
147
157
  /** Explicit denial intent. Carry until the exact predecessor's application is proven. */
148
158
  withdrawals: IRuntimeNetworkPacketGrantV1[];
149
159
  retiredLeases: IRuntimeAuthorityReferenceV1[];
@@ -173,7 +183,7 @@ export const runtimeNetworkProjectionV1Contract = Object.freeze({
173
183
  maximumGrants: 4096,
174
184
  maximumRouterGrants: 96,
175
185
  maximumRouterWithdrawals: 4096,
176
- maximumDnsLeaseMs: 900_000,
186
+ maximumDnsLeaseMs: networkMaximumDnsLeaseMs,
177
187
  positiveTtlSeconds: 5,
178
188
  negativeTtlSeconds: 1,
179
189
  omission: 'no-effect-without-explicit-withdrawal',
@@ -182,11 +192,6 @@ export const runtimeNetworkProjectionV1Contract = Object.freeze({
182
192
  allocationReuse: 'requires-controller-quarantine-and-all-affected-authority-fences',
183
193
  } as const);
184
194
 
185
- const scope = (item: Record<string, unknown>): IRuntimeNetworkNodeScopeV1 => ({
186
- controller: networkController(item.controller), nodeId: networkId(item.nodeId), runtimeNamespace: networkId(item.runtimeNamespace),
187
- });
188
- const sameScope = (left: IRuntimeNetworkNodeScopeV1, right: IRuntimeNetworkNodeScopeV1): boolean =>
189
- networkEqual(left.controller, right.controller) && left.nodeId === right.nodeId && left.runtimeNamespace === right.runtimeNamespace;
190
195
  const ownerKey = (value: { organizationId: string; serviceId: string }): string => networkCanonical([value.organizationId, value.serviceId]);
191
196
  const attemptKey = (value: IRuntimeNetworkWorkloadLeaseV1): string =>
192
197
  networkCanonical([value.organizationId, value.serviceId, value.replicaId, value.attemptId]);
@@ -195,7 +200,7 @@ const sortedReferences = (value: unknown): IRuntimeAuthorityReferenceV1[] =>
195
200
  networkSorted(networkList(value, 256, networkReference), refKey);
196
201
 
197
202
  export const snapshotRuntimeNetworkNodeScopeV1 = (value: unknown): IRuntimeNetworkNodeScopeV1 => networkSafe(() =>
198
- scope(networkRecord(captureNetwork(value), ['controller', 'nodeId', 'runtimeNamespace'])));
203
+ networkScope(networkRecord(captureNetwork(value), ['controller', 'nodeId', 'runtimeNamespace'])));
199
204
 
200
205
  export const snapshotRuntimeNetworkWorkloadLeaseV1 = (value: unknown): IRuntimeNetworkWorkloadLeaseV1 => networkSafe(() => {
201
206
  const item = networkRecord(captureNetwork(value), ['schemaVersion', 'id', 'generation', 'controller', 'nodeId', 'runtimeNamespace',
@@ -215,7 +220,7 @@ export const snapshotRuntimeNetworkWorkloadLeaseV1 = (value: unknown): IRuntimeN
215
220
  requireNetwork(number >= low && number <= high && (high - low === 1 || (number !== low && number !== high)));
216
221
  }
217
222
  requireNetwork(item.dnsServer === gateway);
218
- return { schemaVersion: 1, id: networkId(item.id), generation: networkCounter(item.generation), ...scope(item),
223
+ return { schemaVersion: 1, id: networkId(item.id), generation: networkCounter(item.generation), ...networkScope(item),
219
224
  organizationId: networkId(item.organizationId), assignmentId: networkId(item.assignmentId), serviceId: networkId(item.serviceId),
220
225
  replicaId: networkId(item.replicaId), attemptId: networkId(item.attemptId), protectedAuthority: networkReference(item.protectedAuthority),
221
226
  routerIncarnationId: networkId(item.routerIncarnationId), ipv4: { poolId: networkId(ip.poolId), linkSubnet: ip.linkSubnet,
@@ -312,16 +317,13 @@ export const snapshotRuntimeNetworkProjectionV1 = (value: unknown): IRuntimeNetw
312
317
  'protectedAuthority', 'allocationAuthorities', 'handoff', 'networks', 'memberships', 'endpoints', 'dns', 'withdrawals', 'retiredLeases', 'retiredHandoffs', 'digest',
313
318
  ...(hasRouter ? ['router'] : [])]);
314
319
  requireNetwork(item.schemaVersion === 1);
315
- const dns = networkRecord(item.dns, ['issuedAt', 'notBefore', 'expiresAt']);
316
- const window = { issuedAt: networkCounter(dns.issuedAt), notBefore: networkCounter(dns.notBefore), expiresAt: networkCounter(dns.expiresAt) };
317
- requireNetwork(window.notBefore >= window.issuedAt && window.expiresAt > window.notBefore && window.expiresAt - window.issuedAt <= 900_000);
318
- return { schemaVersion: 1, ...networkRevision(item), ...scope(item),
320
+ return { schemaVersion: 1, ...networkRevision(item), ...networkScope(item),
319
321
  protectedAuthority: snapshotRuntimeNetworkProtectedAuthorityV1(item.protectedAuthority),
320
322
  allocationAuthorities: networkSorted(networkList(item.allocationAuthorities, 16, snapshotRuntimeNetworkProtectedAuthorityV1), refKey),
321
323
  handoff: item.handoff === null ? null : snapshotRuntimeNetworkHandoffLeaseV1(item.handoff),
322
324
  networks: networkSorted(networkList(item.networks, 128, snapshotPrivateNetworkV1), value => value.id),
323
325
  memberships: networkSorted(networkList(item.memberships, 256, snapshotServicePrivateNetworksV1), ownerKey),
324
- endpoints: networkSorted(networkList(item.endpoints, 256, endpoint), value => value.lease.id), dns: window,
326
+ endpoints: networkSorted(networkList(item.endpoints, 256, endpoint), value => value.lease.id), dns: networkDnsWindow(item.dns),
325
327
  withdrawals: networkSorted(networkList(item.withdrawals, 4096, packetGrant), networkCanonical),
326
328
  ...(hasRouter ? { router: snapshotRouterProjection(item.router) } : {}),
327
329
  retiredLeases: sortedReferences(item.retiredLeases), retiredHandoffs: sortedReferences(item.retiredHandoffs), digest: networkDigest(item.digest) };
@@ -336,14 +338,14 @@ const grantsOf = (projection: IRuntimeNetworkProjectionV1): IRuntimeNetworkPacke
336
338
  for (const source of projection.endpoints) {
337
339
  const member = memberships.get(ownerKey(source.lease)); requireNetwork(member);
338
340
  for (const destination of projection.endpoints) {
339
- if (source === destination || (!sameScope(source.lease, projection) && !sameScope(destination.lease, projection))) continue;
341
+ if (source === destination || (!networkSameScope(source.lease, projection) && !networkSameScope(destination.lease, projection))) continue;
340
342
  const target = memberships.get(ownerKey(destination.lease)); requireNetwork(target);
341
343
  if (member.organizationId === target.organizationId && member.attachments.some(attachment =>
342
344
  target.attachments.some(other => other.networkId === attachment.networkId))) {
343
345
  append({ source: networkReferenceOf(source.lease), destination: { kind: 'workload', lease: networkReferenceOf(destination.lease) } });
344
346
  }
345
347
  }
346
- if (!sameScope(source.lease, projection)) continue;
348
+ if (!networkSameScope(source.lease, projection)) continue;
347
349
  if (source.publicEgress) append({ source: networkReferenceOf(source.lease), destination: { kind: 'public' } });
348
350
  for (const id of source.platformEndpointIds) {
349
351
  const platform = projection.protectedAuthority.platformEndpoints.find(item => item.id === id); requireNetwork(platform);
@@ -373,7 +375,7 @@ export const validateRuntimeNetworkProjectionV1 = async (value: unknown): Promis
373
375
  };
374
376
  if (projection.handoff !== null) {
375
377
  await bindRuntimeNetworkHandoffLeaseV1(projection.handoff, allocationAuthority(projection.handoff.protectedAuthority));
376
- requireNetwork(sameScope(projection.handoff, projection));
378
+ requireNetwork(networkSameScope(projection.handoff, projection));
377
379
  requireNetwork(current.pools.some(pool => pool.id === projection.handoff!.transit.poolId && pool.purpose === 'transit'
378
380
  && networkContains(pool.prefix, projection.handoff!.transit.prefix)));
379
381
  }
@@ -413,7 +415,7 @@ export const validateRuntimeNetworkProjectionV1 = async (value: unknown): Promis
413
415
  && current.egressAuthorities.some(owner => owner.nodeId === lease.nodeId && owner.runtimeNamespace === lease.runtimeNamespace));
414
416
  for (const protection of [authority, current]) requireNetwork(protection.pools.some(pool =>
415
417
  pool.id === lease.ipv4.poolId && pool.purpose === 'workload' && networkContains(pool.prefix, lease.ipv4.linkSubnet)));
416
- if (sameScope(lease, projection)) requireNetwork(projection.handoff !== null && lease.routerIncarnationId === projection.handoff.routerIncarnationId);
418
+ if (networkSameScope(lease, projection)) requireNetwork(projection.handoff !== null && lease.routerIncarnationId === projection.handoff.routerIncarnationId);
417
419
  for (const id of endpoint.platformEndpointIds) requireNetwork(current.platformEndpoints.some(platform => platform.id === id));
418
420
  for (const port of endpoint.publishedPorts ?? []) {
419
421
  const claim = networkCanonical([lease.nodeId, port.protocol, port.hostPort]);
@@ -450,7 +452,7 @@ export const composeRuntimeNetworkProjectionDnsViewsV1 = async (
450
452
  ): Promise<IRuntimeNetworkDnsViewV1[]> => {
451
453
  const selected = sourcesValue === undefined ? null : networkList(captureNetwork(sourcesValue), 256, networkReference);
452
454
  const projection = await validateRuntimeNetworkProjectionV1(value);
453
- const local = projection.endpoints.filter(endpoint => sameScope(endpoint.lease, projection));
455
+ const local = projection.endpoints.filter(endpoint => networkSameScope(endpoint.lease, projection));
454
456
  if (selected !== null) {
455
457
  requireNetwork(new Set(selected.map(source => source.id)).size === selected.length);
456
458
  for (const source of selected) requireNetwork(local.some(endpoint => networkEqual(networkReferenceOf(endpoint.lease), source)));
@@ -479,7 +481,7 @@ export const composeRuntimeNetworkProjectionDnsViewsV1 = async (
479
481
  export const bindRuntimeNetworkWorkloadLeaseToAssignmentV1 = async (leaseValue: unknown, assignmentValue: unknown): Promise<IRuntimeNetworkWorkloadLeaseV1> => {
480
482
  try {
481
483
  const lease = snapshotRuntimeNetworkWorkloadLeaseV1(leaseValue), assignment = snapshotRuntimeAssignmentV1(assignmentValue);
482
- requireNetwork((await validateRuntimeAssignmentV1(assignment)).length === 0 && sameScope(lease, assignment)
484
+ requireNetwork((await validateRuntimeAssignmentV1(assignment)).length === 0 && networkSameScope(lease, assignment)
483
485
  && lease.organizationId === assignment.organizationId && lease.assignmentId === assignment.assignmentId
484
486
  && lease.serviceId === assignment.serviceId && lease.replicaId === assignment.replicaId && lease.attemptId === assignment.attemptId
485
487
  && networkEqual(networkReferenceOf(lease), assignment.workload.network)
@@ -503,7 +505,7 @@ export const admitRuntimeNetworkProjectionV1 = async (
503
505
  const trusted = snapshotRuntimeNetworkNodeScopeV1(trustedScopeValue);
504
506
  const applied = appliedPreviousValue === null ? null : networkReference(captureNetwork(appliedPreviousValue));
505
507
  const projection = await validateRuntimeNetworkProjectionV1(incoming);
506
- requireNetwork(sameScope(projection, trusted));
508
+ requireNetwork(networkSameScope(projection, trusted));
507
509
  if (previousInput === null) {
508
510
  requireNetwork(projection.generation === 1 && applied === null && projection.withdrawals.length === 0
509
511
  && (projection.router?.withdrawals.length ?? 0) === 0
@@ -511,7 +513,7 @@ export const admitRuntimeNetworkProjectionV1 = async (
511
513
  return { status: 'accepted', projection };
512
514
  }
513
515
  const previous = await validateRuntimeNetworkProjectionV1(previousInput);
514
- requireNetwork(sameScope(previous, trusted) && previous.id === projection.id);
516
+ requireNetwork(networkSameScope(previous, trusted) && previous.id === projection.id);
515
517
  requireNetwork(applied === null || networkEqual(applied, networkReferenceOf(previous)));
516
518
  if (projection.generation === previous.generation) {
517
519
  requireNetwork(networkEqual(previous, projection)); return { status: 'replay', projection };
@@ -3,8 +3,8 @@ import type { IRuntimeAuthorityReferenceV1, IRuntimeControllerV1 } from './runti
3
3
  import type { IRuntimeNetworkProjectionV1 } from './networkprojection.js';
4
4
  import { snapshotRuntimeNetworkProjectionV1 } from './networkprojection.js';
5
5
  import { snapshotRuntimeSessionBindingV1 } from './runtimesession.js';
6
- import { captureNetwork, failNetwork, networkController, networkDigest, networkEqual, networkHash,
7
- networkRecord, networkReference, networkReferenceOf, networkRevision, networkSafe, requireNetwork } from '../private/networkcontract.js';
6
+ import { captureNetwork, failNetwork, networkController, networkDigest, networkEqual, networkHash, networkRecord,
7
+ networkReference, networkReferenceOf, networkRevision, networkSafe, networkSignature, requireNetwork } from '../private/networkcontract.js';
8
8
 
9
9
  export interface ISignedRuntimeNetworkProjectionV1 {
10
10
  schemaVersion: 1;
@@ -16,8 +16,9 @@ export interface ISignedRuntimeNetworkProjectionV1 {
16
16
 
17
17
  export const snapshotSignedRuntimeNetworkProjectionV1 = (value: unknown): ISignedRuntimeNetworkProjectionV1 => networkSafe(() => {
18
18
  const item = networkRecord(captureNetwork(value), ['schemaVersion', 'authority', 'projection', 'signature']);
19
- requireNetwork(item.schemaVersion === 1 && typeof item.signature === 'string' && /^[a-f0-9]{128}$/.test(item.signature));
20
- return { schemaVersion: 1, authority: networkReference(item.authority), projection: snapshotRuntimeNetworkProjectionV1(item.projection), signature: item.signature };
19
+ requireNetwork(item.schemaVersion === 1);
20
+ return { schemaVersion: 1, authority: networkReference(item.authority), projection: snapshotRuntimeNetworkProjectionV1(item.projection),
21
+ signature: networkSignature(item.signature) };
21
22
  });
22
23
 
23
24
  /**