@serve.zone/interfaces 32.38.0 → 32.39.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.
package/changelog.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-10-02 - 32.39.0
4
+
5
+ ### Maintenance
6
+
7
+ - Update release tooling to tsbuild 5.1.0 and tsdoc 3.0.1 (Node.js types stay 26.6.2 and pnpm 12.6.0 until their seven-day release age).
8
+
9
+ ### Features
10
+
11
+ - A node's managed VPN membership now outlives its network projection.
12
+ `continuesRuntimeManagedVpnMembership(previous, next)` states what a membership is: one
13
+ projection lineage under one protected authority and one handoff lease (its node scope and
14
+ router incarnation), at a later generation (the same generation only as the identical projection). Workload, readiness, egress, router
15
+ selection and DNS window changes keep it; a protection bump, a fresh handoff or a new controller
16
+ epoch ends it. `bindGetRuntimeManagedVpnCredentialSuccessor` binds the credential for a successor
17
+ projection: the same session, transport, hub, authority and key pair as the held credential,
18
+ with only `projection` and `expiresAt` moved and the expiry inside the successor's effective DNS
19
+ window, which must cover the caller's time. A controller keeps a member and its key pair across a
20
+ projection change with it, so the node's hub session survives a workload change.
21
+
3
22
  ## 2026-09-30 - 32.38.0
4
23
 
5
24
  ### Features
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.38.0',
6
+ version: '32.39.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFNBQVM7SUFDbEIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
@@ -7,7 +7,12 @@ export interface IGetRuntimeManagedVpnCredentialRequest {
7
7
  }
8
8
  /** Secret response over the current authenticated physical peer only. Never
9
9
  * persist, hash, log, or include it in a signed network projection. The native
10
- * Noise owner validates and uses the keys; these shapes confer no TUN readiness. */
10
+ * Noise owner validates and uses the keys; these shapes confer no TUN readiness.
11
+ *
12
+ * One response names one exact projection, but its key pair belongs to the node's managed VPN
13
+ * membership, which outlives a projection: a credential for a successor projection of the same
14
+ * membership (`continuesRuntimeManagedVpnMembership`) carries the same transport, hub, authority and
15
+ * keys, bound by `bindGetRuntimeManagedVpnCredentialSuccessor`. */
11
16
  export interface IGetRuntimeManagedVpnCredentialResponse extends IGetRuntimeManagedVpnCredentialRequest {
12
17
  transport: TRuntimeNetworkVpnTransport;
13
18
  /** The cluster hub endpoint this node dials, named by the id its signed projection selected. */
@@ -32,6 +37,24 @@ export declare const snapshotGetRuntimeManagedVpnCredentialResponse: (value: unk
32
37
  /** Content binding only. The caller must resolve the exact current projection
33
38
  * and authenticate/fence the current physical peer before and after native work. */
34
39
  export declare const bindGetRuntimeManagedVpnCredentialRequest: (value: unknown, projectionValue: unknown, trustedSessionValue: unknown) => Promise<IGetRuntimeManagedVpnCredentialRequest>;
40
+ /**
41
+ * Whether `next` continues the managed VPN membership `previous` belongs to: the same projection
42
+ * lineage (`id`), the same protected authority, and the same handoff lease, which names the node
43
+ * scope and carries the node's router incarnation. Both must hold a handoff, because a node without
44
+ * one is no member. `next` is `previous` itself or a later generation; an earlier one, or another
45
+ * projection under the same generation, continues nothing.
46
+ *
47
+ * Everything else a projection states may move under one membership: its workload endpoints, their
48
+ * readiness, observations and egress, its private networks, its router selection and its DNS window.
49
+ * Those are what the member's network carries, never who the member is. A protection bump, a fresh
50
+ * handoff (router reincarnation) or a new controller epoch ends the membership. Whether the hub a
51
+ * credential names is still selected is the credential's question
52
+ * (`bindGetRuntimeManagedVpnCredentialSuccessor`), not the membership's.
53
+ *
54
+ * Validates both complete projections and rejects either that does not validate; a valid pair that
55
+ * is not one membership answers `false`. It samples no clock and fences no session.
56
+ */
57
+ export declare const continuesRuntimeManagedVpnMembership: (previousValue: unknown, nextValue: unknown) => Promise<boolean>;
35
58
  /** Bind the secret response to the original request and the exact selected router
36
59
  * endpoint of the credential's transport, inside the projection's effective DNS window. renewalValue is the
37
60
  * bare lease renewal the caller already verified and admitted for this exact
@@ -39,3 +62,22 @@ export declare const bindGetRuntimeManagedVpnCredentialRequest: (value: unknown,
39
62
  * this helper does not sample a clock, authenticate a transport, admit policy or
40
63
  * prove a running managed connection. */
41
64
  export declare const bindGetRuntimeManagedVpnCredentialResponse: (value: unknown, requestValue: unknown, projectionValue: unknown, trustedSessionValue: unknown, nowValue: unknown, renewalValue?: unknown | null) => Promise<IGetRuntimeManagedVpnCredentialResponse>;
65
+ /**
66
+ * Bind the credential a node holds under its successor projection: `value` names `projectionValue`
67
+ * and is bound exactly as `bindGetRuntimeManagedVpnCredentialResponse` binds the answer to
68
+ * `{ session: trustedSession, projection }`, inside that projection's effective DNS window
69
+ * (`renewalValue`, as there). It must also continue `previousValue`, the credential issued for
70
+ * `previousProjectionValue` over the same session: the two projections are one membership
71
+ * (`continuesRuntimeManagedVpnMembership`) and the transport, hub, authority, server key and client
72
+ * key pair are unchanged. Only `projection` and `expiresAt` move. The current effective window
73
+ * alone bounds the successor, so a membership whose current window does not cover the
74
+ * caller-qualified `nowValue` binds no successor; the held credential's own expiry is not
75
+ * consulted, because a renewal or a successor may have moved the window past it.
76
+ *
77
+ * The issuer binds the successor it states for a member, so the node's session and key pair survive
78
+ * a workload, readiness or policy change; the node binds a fresh answer with it to tell a successor,
79
+ * which keeps its live session, from a new membership, which replaces it. A previous credential for
80
+ * the same projection binds too, which is how a renewal moves the expiry. Content binding only, as
81
+ * for the response binder.
82
+ */
83
+ export declare const bindGetRuntimeManagedVpnCredentialSuccessor: (value: unknown, previousValue: unknown, previousProjectionValue: unknown, projectionValue: unknown, trustedSessionValue: unknown, nowValue: unknown, renewalValue?: unknown | null) => Promise<IGetRuntimeManagedVpnCredentialResponse>;
@@ -1,6 +1,6 @@
1
1
  import { runtimeNetworkAddressPlanContract } from './networkaddressplan.js';
2
2
  import { snapshotRuntimeSessionBinding } from './runtimesession.js';
3
- import { validateRuntimeNetworkProjection } from './networkprojection.js';
3
+ import { snapshotRuntimeNetworkProjection, validateRuntimeNetworkProjection } from './networkprojection.js';
4
4
  import { snapshotRuntimeNetworkDnsLeaseRenewal, validateRuntimeNetworkDnsLeaseRenewal } from './networklease.js';
5
5
  import { captureNetwork, failNetwork, networkAuthorityId, networkCounter, networkEffectiveDnsWindow, networkEqual, networkId, networkIpv4, networkNoiseKey, networkRecord, networkReference, networkReferenceOf, networkSafe, networkSameScope, requireNetwork } from '../private/networkcontract.js';
6
6
  export const snapshotGetRuntimeManagedVpnCredentialRequest = (value) => networkSafe(() => {
@@ -41,6 +41,62 @@ export const bindGetRuntimeManagedVpnCredentialRequest = async (value, projectio
41
41
  return failNetwork();
42
42
  }
43
43
  };
44
+ /**
45
+ * The membership rule of `continuesRuntimeManagedVpnMembership`, over projections already validated.
46
+ * A valid projection's handoff lease names the projection's own node scope, so one handoff
47
+ * reference is one node scope and the scope needs no comparison of its own.
48
+ */
49
+ const continuesMembership = (previous, next) => previous.handoff !== null && next.handoff !== null && previous.id === next.id
50
+ && networkEqual(networkReferenceOf(previous.protectedAuthority), networkReferenceOf(next.protectedAuthority))
51
+ && networkEqual(networkReferenceOf(previous.handoff), networkReferenceOf(next.handoff))
52
+ && (next.generation > previous.generation || networkEqual(networkReferenceOf(previous), networkReferenceOf(next)));
53
+ /** What a membership's credential keeps across its projections: everything but the projection and the expiry. */
54
+ const credentialIdentity = (value) => ({
55
+ transport: value.transport, hub: value.hub, authorityId: value.authorityId, serverPublicKey: value.serverPublicKey,
56
+ clientPublicKey: value.clientPublicKey, clientPrivateKey: value.clientPrivateKey,
57
+ });
58
+ /**
59
+ * Whether `next` continues the managed VPN membership `previous` belongs to: the same projection
60
+ * lineage (`id`), the same protected authority, and the same handoff lease, which names the node
61
+ * scope and carries the node's router incarnation. Both must hold a handoff, because a node without
62
+ * one is no member. `next` is `previous` itself or a later generation; an earlier one, or another
63
+ * projection under the same generation, continues nothing.
64
+ *
65
+ * Everything else a projection states may move under one membership: its workload endpoints, their
66
+ * readiness, observations and egress, its private networks, its router selection and its DNS window.
67
+ * Those are what the member's network carries, never who the member is. A protection bump, a fresh
68
+ * handoff (router reincarnation) or a new controller epoch ends the membership. Whether the hub a
69
+ * credential names is still selected is the credential's question
70
+ * (`bindGetRuntimeManagedVpnCredentialSuccessor`), not the membership's.
71
+ *
72
+ * Validates both complete projections and rejects either that does not validate; a valid pair that
73
+ * is not one membership answers `false`. It samples no clock and fences no session.
74
+ */
75
+ export const continuesRuntimeManagedVpnMembership = async (previousValue, nextValue) => {
76
+ let previous, next;
77
+ try {
78
+ const previousInput = snapshotRuntimeNetworkProjection(previousValue), nextInput = snapshotRuntimeNetworkProjection(nextValue);
79
+ previous = await validateRuntimeNetworkProjection(previousInput);
80
+ next = await validateRuntimeNetworkProjection(nextInput);
81
+ }
82
+ catch {
83
+ return failNetwork();
84
+ }
85
+ return continuesMembership(previous, next);
86
+ };
87
+ /** The response binding over inputs already captured and validated. */
88
+ const bindResponse = (response, request, projection, renewal, trusted, now) => {
89
+ const window = networkEffectiveDnsWindow(projection, renewal);
90
+ bindRequest(request, projection, trusted);
91
+ requireNetwork(networkEqual(response.session, request.session) && networkEqual(response.projection, request.projection));
92
+ requireNetwork(projection.router?.platformEndpointIds.includes(response.hub.id));
93
+ const endpoint = projection.protectedAuthority.platformEndpoints.find(item => item.id === response.hub.id);
94
+ const expected = runtimeNetworkAddressPlanContract.hubTransports.find(row => row.transport === response.transport);
95
+ requireNetwork(endpoint && expected && endpoint.protocol === expected.protocol
96
+ && endpoint.address === response.hub.address && endpoint.port === response.hub.port);
97
+ requireNetwork(window.notBefore <= now && now < response.expiresAt && response.expiresAt <= window.expiresAt);
98
+ return response;
99
+ };
44
100
  /** Bind the secret response to the original request and the exact selected router
45
101
  * endpoint of the credential's transport, inside the projection's effective DNS window. renewalValue is the
46
102
  * bare lease renewal the caller already verified and admitted for this exact
@@ -55,19 +111,49 @@ export const bindGetRuntimeManagedVpnCredentialResponse = async (value, requestV
55
111
  const renewalInput = renewalValue === null ? null : snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue);
56
112
  const projection = await validateRuntimeNetworkProjection(projectionValue);
57
113
  const renewal = renewalInput === null ? null : await validateRuntimeNetworkDnsLeaseRenewal(renewalInput);
58
- const window = networkEffectiveDnsWindow(projection, renewal);
59
- bindRequest(request, projection, trusted);
60
- requireNetwork(networkEqual(response.session, request.session) && networkEqual(response.projection, request.projection));
61
- requireNetwork(projection.router?.platformEndpointIds.includes(response.hub.id));
62
- const endpoint = projection.protectedAuthority.platformEndpoints.find(item => item.id === response.hub.id);
63
- const expected = runtimeNetworkAddressPlanContract.hubTransports.find(row => row.transport === response.transport);
64
- requireNetwork(endpoint && expected && endpoint.protocol === expected.protocol
65
- && endpoint.address === response.hub.address && endpoint.port === response.hub.port);
66
- requireNetwork(window.notBefore <= now && now < response.expiresAt && response.expiresAt <= window.expiresAt);
67
- return response;
114
+ return bindResponse(response, request, projection, renewal, trusted, now);
115
+ }
116
+ catch {
117
+ return failNetwork();
118
+ }
119
+ };
120
+ /**
121
+ * Bind the credential a node holds under its successor projection: `value` names `projectionValue`
122
+ * and is bound exactly as `bindGetRuntimeManagedVpnCredentialResponse` binds the answer to
123
+ * `{ session: trustedSession, projection }`, inside that projection's effective DNS window
124
+ * (`renewalValue`, as there). It must also continue `previousValue`, the credential issued for
125
+ * `previousProjectionValue` over the same session: the two projections are one membership
126
+ * (`continuesRuntimeManagedVpnMembership`) and the transport, hub, authority, server key and client
127
+ * key pair are unchanged. Only `projection` and `expiresAt` move. The current effective window
128
+ * alone bounds the successor, so a membership whose current window does not cover the
129
+ * caller-qualified `nowValue` binds no successor; the held credential's own expiry is not
130
+ * consulted, because a renewal or a successor may have moved the window past it.
131
+ *
132
+ * The issuer binds the successor it states for a member, so the node's session and key pair survive
133
+ * a workload, readiness or policy change; the node binds a fresh answer with it to tell a successor,
134
+ * which keeps its live session, from a new membership, which replaces it. A previous credential for
135
+ * the same projection binds too, which is how a renewal moves the expiry. Content binding only, as
136
+ * for the response binder.
137
+ */
138
+ export const bindGetRuntimeManagedVpnCredentialSuccessor = async (value, previousValue, previousProjectionValue, projectionValue, trustedSessionValue, nowValue, renewalValue = null) => {
139
+ try {
140
+ const response = snapshotGetRuntimeManagedVpnCredentialResponse(value);
141
+ const previous = snapshotGetRuntimeManagedVpnCredentialResponse(previousValue);
142
+ const previousInput = snapshotRuntimeNetworkProjection(previousProjectionValue);
143
+ const projectionInput = snapshotRuntimeNetworkProjection(projectionValue);
144
+ const trusted = snapshotRuntimeSessionBinding(trustedSessionValue), now = networkCounter(nowValue);
145
+ const renewalInput = renewalValue === null ? null : snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue);
146
+ const previousProjection = await validateRuntimeNetworkProjection(previousInput);
147
+ const projection = await validateRuntimeNetworkProjection(projectionInput);
148
+ const renewal = renewalInput === null ? null : await validateRuntimeNetworkDnsLeaseRenewal(renewalInput);
149
+ requireNetwork(continuesMembership(previousProjection, projection));
150
+ requireNetwork(networkEqual(previous.session, trusted)
151
+ && networkEqual(previous.projection, networkReferenceOf(previousProjection)));
152
+ requireNetwork(networkEqual(credentialIdentity(previous), credentialIdentity(response)));
153
+ return bindResponse(response, { session: trusted, projection: networkReferenceOf(projection) }, projection, renewal, trusted, now);
68
154
  }
69
155
  catch {
70
156
  return failNetwork();
71
157
  }
72
158
  };
73
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV0d29ya2NyZWRlbnRpYWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL25ldHdvcmtjcmVkZW50aWFsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxpQ0FBaUMsRUFBb0MsTUFBTSx5QkFBeUIsQ0FBQztBQUM5RyxPQUFPLEVBQUUsNkJBQTZCLEVBQStCLE1BQU0scUJBQXFCLENBQUM7QUFDakcsT0FBTyxFQUFFLGdDQUFnQyxFQUFrQyxNQUFNLHdCQUF3QixDQUFDO0FBQzFHLE9BQU8sRUFBRSxxQ0FBcUMsRUFBRSxxQ0FBcUMsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQ2pILE9BQU8sRUFBRSxjQUFjLEVBQUUsV0FBVyxFQUFFLGtCQUFrQixFQUFFLGNBQWMsRUFBRSx5QkFBeUIsRUFBRSxZQUFZLEVBQy9HLFNBQVMsRUFBRSxXQUFXLEVBQUUsZUFBZSxFQUFFLGFBQWEsRUFBRSxnQkFBZ0IsRUFBRSxrQkFBa0IsRUFBRSxXQUFXLEVBQ3pHLGdCQUFnQixFQUFFLGNBQWMsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBMEIxRSxNQUFNLENBQUMsTUFBTSw2Q0FBNkMsR0FBRyxDQUFDLEtBQWMsRUFBMEMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUU7SUFDeEksTUFBTSxJQUFJLEdBQUcsYUFBYSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFNBQVMsRUFBRSxZQUFZLENBQUMsQ0FBQyxDQUFDO0lBQzdFLE9BQU8sRUFBRSxPQUFPLEVBQUUsNkJBQTZCLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFLFVBQVUsRUFBRSxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQztBQUNqSCxDQUFDLENBQUMsQ0FBQztBQUVILE1BQU0sV0FBVyxHQUFHLENBQUMsS0FBYyxFQUErQixFQUFFO0lBQ2xFLE1BQU0sR0FBRyxHQUFHLGlDQUFpQyxDQUFD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
159
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmV0d29ya2NyZWRlbnRpYWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL25ldHdvcmtjcmVkZW50aWFsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxpQ0FBaUMsRUFBb0MsTUFBTSx5QkFBeUIsQ0FBQztBQUM5RyxPQUFPLEVBQUUsNkJBQTZCLEVBQStCLE1BQU0scUJBQXFCLENBQUM7QUFDakcsT0FBTyxFQUFFLGdDQUFnQyxFQUFFLGdDQUFnQyxFQUN6QyxNQUFNLHdCQUF3QixDQUFDO0FBQ2pFLE9BQU8sRUFBRSxxQ0FBcUMsRUFBRSxxQ0FBcUMsRUFDOUMsTUFBTSxtQkFBbUIsQ0FBQztBQUNqRSxPQUFPLEVBQUUsY0FBYyxFQUFFLFdBQVcsRUFBRSxrQkFBa0IsRUFBRSxjQUFjLEVBQUUseUJBQXlCLEVBQUUsWUFBWSxFQUMvRyxTQUFTLEVBQUUsV0FBVyxFQUFFLGVBQWUsRUFBRSxhQUFhLEVBQUUsZ0JBQWdCLEVBQUUsa0JBQWtCLEVBQUUsV0FBVyxFQUN6RyxnQkFBZ0IsRUFBRSxjQUFjLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQStCMUUsTUFBTSxDQUFDLE1BQU0sNkNBQTZDLEdBQUcsQ0FBQyxLQUFjLEVBQTBDLEVBQUUsQ0FBQyxXQUFXLENBQUMsR0FBRyxFQUFFO0lBQ3hJLE1BQU0sSUFBSSxHQUFHLGFBQWEsQ0FBQyxjQUFjLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxTQUFTLEVBQUUsWUFBWSxDQUFDLENBQUMsQ0FBQztJQUM3RSxPQUFPLEVBQUUsT0FBTyxFQUFFLDZCQUE2QixDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxVQUFVLEVBQUUsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7QUFDakgsQ0FBQyxDQUFDLENBQUM7QUFFSCxNQUFNLFdBQVcsR0FBRyxDQUFDLEtBQWMsRUFBK0IsRUFBRTtJQUNsRSxNQUFNLEdBQUcsR0FB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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "32.38.0",
3
+ "version": "32.39.0",
4
4
  "private": false,
5
5
  "description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
6
6
  "exports": {
@@ -27,8 +27,8 @@
27
27
  "@tsclass/tsclass": "^9.5.1"
28
28
  },
29
29
  "devDependencies": {
30
- "@git.zone/tsbuild": "^5.0.0",
31
- "@git.zone/tsdoc": "^2.2.2",
30
+ "@git.zone/tsbuild": "^5.1.0",
31
+ "@git.zone/tsdoc": "^3.0.1",
32
32
  "@git.zone/tsrun": "^3.0.2",
33
33
  "@git.zone/tstest": "6.3.2",
34
34
  "@types/node": "26.6.2"
package/readme.md CHANGED
@@ -728,6 +728,32 @@ Expiry must be after the caller-qualified time and no later than the referenced
728
728
  projection's effective DNS window (see [DNS Lease Renewals](#dns-lease-renewals));
729
729
  acceptance before that window starts rejects.
730
730
 
731
+ A credential names one projection, but its key pair belongs to the node's managed VPN
732
+ membership, which outlives it. Every workload add, removal, readiness change or policy change
733
+ signs a new projection generation; none of them changes who the member is.
734
+ `continuesRuntimeManagedVpnMembership(previous, next)` is that rule: `next` continues the
735
+ membership when it is the same projection lineage (`id`) under the same protected authority and
736
+ the same handoff lease (which names the node scope and carries the router incarnation), and is
737
+ `previous` itself or a later generation. A protection bump, a fresh handoff (router
738
+ reincarnation) or a new controller epoch is a new membership; an earlier generation or another
739
+ projection of the same generation continues nothing. It validates both projections and rejects
740
+ one that does not validate.
741
+
742
+ `bindGetRuntimeManagedVpnCredentialSuccessor(response, previous, previousProjection, projection,
743
+ trustedSession, now, renewal = null)` binds the credential for the successor. `response` is bound
744
+ exactly as the response binder binds an answer to `{ session: trustedSession, projection }`, so
745
+ the hub must still be selected and the expiry stays inside the successor's effective DNS window.
746
+ It must also continue `previous`, the credential issued for `previousProjection` over the same
747
+ session: the two projections are one membership and the transport, hub, authority, server key and
748
+ client key pair are unchanged. Only `projection` and `expiresAt` move. The current effective
749
+ window is the one limit: a membership whose current window does not cover `now` binds no
750
+ successor, while the held credential's own expiry is not consulted, because a renewal or a
751
+ successor may already have moved the window past it. The issuer binds the successor it states
752
+ for a member with it, so the node keeps its key pair and its hub session across a projection
753
+ change; a node binds a fresh answer with it to tell a successor, which keeps the live session,
754
+ from a new membership, which replaces it. A previous credential for the same projection binds as
755
+ its own successor, which is how a DNS lease renewal moves the expiry.
756
+
731
757
  These helpers perform content validation. Callers authenticate and fence the
732
758
  current physical peer and projection before and after native work, qualify time
733
759
  independently, and retain ownership through revocation and shutdown. The native
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.38.0',
6
+ version: '32.39.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
@@ -1,8 +1,10 @@
1
1
  import type { IRuntimeAuthorityReference } from './runtimeassignment.js';
2
2
  import { runtimeNetworkAddressPlanContract, type TRuntimeNetworkVpnTransport } from './networkaddressplan.js';
3
3
  import { snapshotRuntimeSessionBinding, type IRuntimeSessionBinding } from './runtimesession.js';
4
- import { validateRuntimeNetworkProjection, type IRuntimeNetworkProjection } from './networkprojection.js';
5
- import { snapshotRuntimeNetworkDnsLeaseRenewal, validateRuntimeNetworkDnsLeaseRenewal } from './networklease.js';
4
+ import { snapshotRuntimeNetworkProjection, validateRuntimeNetworkProjection,
5
+ type IRuntimeNetworkProjection } from './networkprojection.js';
6
+ import { snapshotRuntimeNetworkDnsLeaseRenewal, validateRuntimeNetworkDnsLeaseRenewal,
7
+ type IRuntimeNetworkDnsLeaseRenewal } from './networklease.js';
6
8
  import { captureNetwork, failNetwork, networkAuthorityId, networkCounter, networkEffectiveDnsWindow, networkEqual,
7
9
  networkId, networkIpv4, networkNoiseKey, networkRecord, networkReference, networkReferenceOf, networkSafe,
8
10
  networkSameScope, requireNetwork } from '../private/networkcontract.js';
@@ -14,7 +16,12 @@ export interface IGetRuntimeManagedVpnCredentialRequest {
14
16
 
15
17
  /** Secret response over the current authenticated physical peer only. Never
16
18
  * persist, hash, log, or include it in a signed network projection. The native
17
- * Noise owner validates and uses the keys; these shapes confer no TUN readiness. */
19
+ * Noise owner validates and uses the keys; these shapes confer no TUN readiness.
20
+ *
21
+ * One response names one exact projection, but its key pair belongs to the node's managed VPN
22
+ * membership, which outlives a projection: a credential for a successor projection of the same
23
+ * membership (`continuesRuntimeManagedVpnMembership`) carries the same transport, hub, authority and
24
+ * keys, bound by `bindGetRuntimeManagedVpnCredentialSuccessor`. */
18
25
  export interface IGetRuntimeManagedVpnCredentialResponse extends IGetRuntimeManagedVpnCredentialRequest {
19
26
  transport: TRuntimeNetworkVpnTransport;
20
27
  /** The cluster hub endpoint this node dials, named by the id its signed projection selected. */
@@ -72,6 +79,66 @@ export const bindGetRuntimeManagedVpnCredentialRequest = async (value: unknown,
72
79
  } catch { return failNetwork(); }
73
80
  };
74
81
 
82
+ /**
83
+ * The membership rule of `continuesRuntimeManagedVpnMembership`, over projections already validated.
84
+ * A valid projection's handoff lease names the projection's own node scope, so one handoff
85
+ * reference is one node scope and the scope needs no comparison of its own.
86
+ */
87
+ const continuesMembership = (previous: IRuntimeNetworkProjection, next: IRuntimeNetworkProjection): boolean =>
88
+ previous.handoff !== null && next.handoff !== null && previous.id === next.id
89
+ && networkEqual(networkReferenceOf(previous.protectedAuthority), networkReferenceOf(next.protectedAuthority))
90
+ && networkEqual(networkReferenceOf(previous.handoff), networkReferenceOf(next.handoff))
91
+ && (next.generation > previous.generation || networkEqual(networkReferenceOf(previous), networkReferenceOf(next)));
92
+
93
+ /** What a membership's credential keeps across its projections: everything but the projection and the expiry. */
94
+ const credentialIdentity = (value: IGetRuntimeManagedVpnCredentialResponse) => ({
95
+ transport: value.transport, hub: value.hub, authorityId: value.authorityId, serverPublicKey: value.serverPublicKey,
96
+ clientPublicKey: value.clientPublicKey, clientPrivateKey: value.clientPrivateKey,
97
+ });
98
+
99
+ /**
100
+ * Whether `next` continues the managed VPN membership `previous` belongs to: the same projection
101
+ * lineage (`id`), the same protected authority, and the same handoff lease, which names the node
102
+ * scope and carries the node's router incarnation. Both must hold a handoff, because a node without
103
+ * one is no member. `next` is `previous` itself or a later generation; an earlier one, or another
104
+ * projection under the same generation, continues nothing.
105
+ *
106
+ * Everything else a projection states may move under one membership: its workload endpoints, their
107
+ * readiness, observations and egress, its private networks, its router selection and its DNS window.
108
+ * Those are what the member's network carries, never who the member is. A protection bump, a fresh
109
+ * handoff (router reincarnation) or a new controller epoch ends the membership. Whether the hub a
110
+ * credential names is still selected is the credential's question
111
+ * (`bindGetRuntimeManagedVpnCredentialSuccessor`), not the membership's.
112
+ *
113
+ * Validates both complete projections and rejects either that does not validate; a valid pair that
114
+ * is not one membership answers `false`. It samples no clock and fences no session.
115
+ */
116
+ export const continuesRuntimeManagedVpnMembership = async (previousValue: unknown, nextValue: unknown): Promise<boolean> => {
117
+ let previous: IRuntimeNetworkProjection, next: IRuntimeNetworkProjection;
118
+ try {
119
+ const previousInput = snapshotRuntimeNetworkProjection(previousValue), nextInput = snapshotRuntimeNetworkProjection(nextValue);
120
+ previous = await validateRuntimeNetworkProjection(previousInput);
121
+ next = await validateRuntimeNetworkProjection(nextInput);
122
+ } catch { return failNetwork(); }
123
+ return continuesMembership(previous, next);
124
+ };
125
+
126
+ /** The response binding over inputs already captured and validated. */
127
+ const bindResponse = (response: IGetRuntimeManagedVpnCredentialResponse, request: IGetRuntimeManagedVpnCredentialRequest,
128
+ projection: IRuntimeNetworkProjection, renewal: IRuntimeNetworkDnsLeaseRenewal | null, trusted: IRuntimeSessionBinding,
129
+ now: number): IGetRuntimeManagedVpnCredentialResponse => {
130
+ const window = networkEffectiveDnsWindow(projection, renewal);
131
+ bindRequest(request, projection, trusted);
132
+ requireNetwork(networkEqual(response.session, request.session) && networkEqual(response.projection, request.projection));
133
+ requireNetwork(projection.router?.platformEndpointIds.includes(response.hub.id));
134
+ const endpoint = projection.protectedAuthority.platformEndpoints.find(item => item.id === response.hub.id);
135
+ const expected = runtimeNetworkAddressPlanContract.hubTransports.find(row => row.transport === response.transport);
136
+ requireNetwork(endpoint && expected && endpoint.protocol === expected.protocol
137
+ && endpoint.address === response.hub.address && endpoint.port === response.hub.port);
138
+ requireNetwork(window.notBefore <= now && now < response.expiresAt && response.expiresAt <= window.expiresAt);
139
+ return response;
140
+ };
141
+
75
142
  /** Bind the secret response to the original request and the exact selected router
76
143
  * endpoint of the credential's transport, inside the projection's effective DNS window. renewalValue is the
77
144
  * bare lease renewal the caller already verified and admitted for this exact
@@ -88,15 +155,45 @@ export const bindGetRuntimeManagedVpnCredentialResponse = async (value: unknown,
88
155
  const renewalInput = renewalValue === null ? null : snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue);
89
156
  const projection = await validateRuntimeNetworkProjection(projectionValue);
90
157
  const renewal = renewalInput === null ? null : await validateRuntimeNetworkDnsLeaseRenewal(renewalInput);
91
- const window = networkEffectiveDnsWindow(projection, renewal);
92
- bindRequest(request, projection, trusted);
93
- requireNetwork(networkEqual(response.session, request.session) && networkEqual(response.projection, request.projection));
94
- requireNetwork(projection.router?.platformEndpointIds.includes(response.hub.id));
95
- const endpoint = projection.protectedAuthority.platformEndpoints.find(item => item.id === response.hub.id);
96
- const expected = runtimeNetworkAddressPlanContract.hubTransports.find(row => row.transport === response.transport);
97
- requireNetwork(endpoint && expected && endpoint.protocol === expected.protocol
98
- && endpoint.address === response.hub.address && endpoint.port === response.hub.port);
99
- requireNetwork(window.notBefore <= now && now < response.expiresAt && response.expiresAt <= window.expiresAt);
100
- return response;
158
+ return bindResponse(response, request, projection, renewal, trusted, now);
159
+ } catch { return failNetwork(); }
160
+ };
161
+
162
+ /**
163
+ * Bind the credential a node holds under its successor projection: `value` names `projectionValue`
164
+ * and is bound exactly as `bindGetRuntimeManagedVpnCredentialResponse` binds the answer to
165
+ * `{ session: trustedSession, projection }`, inside that projection's effective DNS window
166
+ * (`renewalValue`, as there). It must also continue `previousValue`, the credential issued for
167
+ * `previousProjectionValue` over the same session: the two projections are one membership
168
+ * (`continuesRuntimeManagedVpnMembership`) and the transport, hub, authority, server key and client
169
+ * key pair are unchanged. Only `projection` and `expiresAt` move. The current effective window
170
+ * alone bounds the successor, so a membership whose current window does not cover the
171
+ * caller-qualified `nowValue` binds no successor; the held credential's own expiry is not
172
+ * consulted, because a renewal or a successor may have moved the window past it.
173
+ *
174
+ * The issuer binds the successor it states for a member, so the node's session and key pair survive
175
+ * a workload, readiness or policy change; the node binds a fresh answer with it to tell a successor,
176
+ * which keeps its live session, from a new membership, which replaces it. A previous credential for
177
+ * the same projection binds too, which is how a renewal moves the expiry. Content binding only, as
178
+ * for the response binder.
179
+ */
180
+ export const bindGetRuntimeManagedVpnCredentialSuccessor = async (value: unknown, previousValue: unknown,
181
+ previousProjectionValue: unknown, projectionValue: unknown, trustedSessionValue: unknown, nowValue: unknown,
182
+ renewalValue: unknown | null = null): Promise<IGetRuntimeManagedVpnCredentialResponse> => {
183
+ try {
184
+ const response = snapshotGetRuntimeManagedVpnCredentialResponse(value);
185
+ const previous = snapshotGetRuntimeManagedVpnCredentialResponse(previousValue);
186
+ const previousInput = snapshotRuntimeNetworkProjection(previousProjectionValue);
187
+ const projectionInput = snapshotRuntimeNetworkProjection(projectionValue);
188
+ const trusted = snapshotRuntimeSessionBinding(trustedSessionValue), now = networkCounter(nowValue);
189
+ const renewalInput = renewalValue === null ? null : snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue);
190
+ const previousProjection = await validateRuntimeNetworkProjection(previousInput);
191
+ const projection = await validateRuntimeNetworkProjection(projectionInput);
192
+ const renewal = renewalInput === null ? null : await validateRuntimeNetworkDnsLeaseRenewal(renewalInput);
193
+ requireNetwork(continuesMembership(previousProjection, projection));
194
+ requireNetwork(networkEqual(previous.session, trusted)
195
+ && networkEqual(previous.projection, networkReferenceOf(previousProjection)));
196
+ requireNetwork(networkEqual(credentialIdentity(previous), credentialIdentity(response)));
197
+ return bindResponse(response, { session: trusted, projection: networkReferenceOf(projection) }, projection, renewal, trusted, now);
101
198
  } catch { return failNetwork(); }
102
199
  };