@serve.zone/interfaces 31.6.0 → 31.8.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 (35) hide show
  1. package/changelog.md +47 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/data/index.d.ts +1 -0
  4. package/dist_ts/data/index.js +2 -1
  5. package/dist_ts/data/networkcredential.d.ts +10 -4
  6. package/dist_ts/data/networkcredential.js +14 -8
  7. package/dist_ts/data/networkdelivery.d.ts +21 -0
  8. package/dist_ts/data/networkdelivery.js +35 -7
  9. package/dist_ts/data/networklease.d.ts +110 -0
  10. package/dist_ts/data/networklease.js +159 -0
  11. package/dist_ts/data/networkprojection.d.ts +10 -5
  12. package/dist_ts/data/networkprojection.js +15 -22
  13. package/dist_ts/data/networksigning.js +5 -4
  14. package/dist_ts/private/networkcontract.d.ts +16 -0
  15. package/dist_ts/private/networkcontract.js +31 -1
  16. package/dist_ts/requests/cluster.d.ts +16 -0
  17. package/dist_ts/requests/cluster.js +1 -1
  18. package/dist_ts/requests/node.d.ts +31 -0
  19. package/dist_ts/requests/runtimesession.d.ts +14 -0
  20. package/dist_ts/runtime.networkprojection.d.ts +14 -0
  21. package/dist_ts/runtime.networkprojection.js +68 -19
  22. package/package.json +2 -2
  23. package/readme.md +144 -3
  24. package/ts/00_commitinfo_data.ts +1 -1
  25. package/ts/data/index.ts +1 -0
  26. package/ts/data/networkcredential.ts +19 -9
  27. package/ts/data/networkdelivery.ts +49 -6
  28. package/ts/data/networklease.ts +229 -0
  29. package/ts/data/networkprojection.ts +28 -26
  30. package/ts/data/networksigning.ts +5 -4
  31. package/ts/private/networkcontract.ts +34 -0
  32. package/ts/requests/cluster.ts +20 -0
  33. package/ts/requests/node.ts +35 -0
  34. package/ts/requests/runtimesession.ts +17 -0
  35. package/ts/runtime.networkprojection.ts +87 -20
@@ -150,6 +150,37 @@ export interface IReq_Any_Cloudly_DeleteNodeById extends plugins.typedrequestInt
150
150
  ok: boolean;
151
151
  };
152
152
  }
153
+ /**
154
+ * An administrator states where one node's Corestore control API answers.
155
+ *
156
+ * Corestore is node-local, so one relay serving a whole cluster must be told per node where to
157
+ * dial; the cluster-local alias every component used while Docker Swarm resolved it is exactly
158
+ * what stops resolving. The endpoint is control-plane state Cloudly owns and the relay reads,
159
+ * so an administrator sets it here rather than a relay discovering it.
160
+ *
161
+ * Cloudly validates the endpoint with `validateClusterNodeCorestore` before it writes, takes the
162
+ * node's own real-write fence inside the writing transaction so a concurrent deletion conflicts
163
+ * instead of leaving an endpoint behind for a node that is gone, and pushes the cluster
164
+ * configuration afterwards so every connected relay learns the change without reconnecting.
165
+ *
166
+ * `endpoint: null` clears it, which is the same fact as never having set one: the node runs no
167
+ * Corestore the relay may reach, and an inventory for it answers
168
+ * `errorCode: 'CORESTORE_ENDPOINT_UNKNOWN'` rather than guessing a hostname. The endpoint says
169
+ * where, never with what — no credential travels in this request or in the node document.
170
+ */
171
+ export interface IReq_Any_Cloudly_SetClusterNodeCorestoreEndpoint extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Any_Cloudly_SetClusterNodeCorestoreEndpoint> {
172
+ method: 'setClusterNodeCorestoreEndpoint';
173
+ request: {
174
+ identity: IIdentity;
175
+ nodeId: string;
176
+ /** A canonical `http:` or `https:` origin, or `null` to state that this node has none. */
177
+ endpoint: string | null;
178
+ };
179
+ response: {
180
+ /** The node as it now stands, so a caller needs no second read to see what it set. */
181
+ node: IClusterNode;
182
+ };
183
+ }
153
184
  export interface IRequest_Spark_Cloudly_SendHeartbeat extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Spark_Cloudly_SendHeartbeat> {
154
185
  method: 'sparkNodeHeartbeat';
155
186
  request: {
@@ -28,6 +28,20 @@ export interface IReq_Controller_Pallet_ApplyRuntimeNetworkProjectionV1 extends
28
28
  request: network.IApplyRuntimeNetworkProjectionRequestV1;
29
29
  response: network.IApplyRuntimeNetworkAuthorityResponseV1;
30
30
  }
31
+ /**
32
+ * A fresh DNS window for the node's exact admitted projection; an ACK asserts admission only.
33
+ *
34
+ * The controller sends it while it is reachable. A cluster relay sends the renewals it holds
35
+ * while it has no controller session, under the binding the node obtained itself, which is why
36
+ * a node admits it from whoever holds its current connection and trusts only the signature.
37
+ * Either sender delivers a window once its slot has opened and never before, because a node
38
+ * refuses a renewal it could not use at once; delivering it again once the slot opens is enough.
39
+ */
40
+ export interface IReq_Controller_Pallet_ApplyRuntimeNetworkDnsLeaseRenewal extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Controller_Pallet_ApplyRuntimeNetworkDnsLeaseRenewal> {
41
+ method: 'applyRuntimeNetworkDnsLeaseRenewal';
42
+ request: network.IApplyRuntimeNetworkDnsLeaseRenewalRequest;
43
+ response: network.IApplyRuntimeNetworkDnsLeaseRenewalResponse;
44
+ }
31
45
  /**
32
46
  * WSS-only Pallet bootstrap. Derive physical peer/idempotency server-side; the registering
33
47
  * node process is named by `request.nodeInstanceNonce`, because one relay socket may carry
@@ -1,5 +1,6 @@
1
1
  import * as plugins from './plugins.runtime.js';
2
2
  import type { ISignedRuntimeNetworkProjectionV1 } from './data/networksigning.js';
3
+ import type { ISignedRuntimeNetworkDnsLeaseRenewal } from './data/networklease.js';
3
4
  /** The private KeyObject is process-local. Persist only through the controller's encrypted secret store. */
4
5
  export declare const createRuntimeNetworkSigningKeyV1: () => {
5
6
  publicKey: string;
@@ -13,3 +14,16 @@ export declare const signRuntimeNetworkProjectionV1: (projectionValue: unknown,
13
14
  * admission, live-session fencing, allocation barriers and conservative DNS time checks.
14
15
  */
15
16
  export declare const verifyRuntimeNetworkProjectionV1: (value: unknown, trustedAuthorityValue: unknown, trustedScopeValue: unknown) => Promise<ISignedRuntimeNetworkProjectionV1>;
17
+ /** Sign only a validated renewal, with the same authority and key that sign this controller's projections. */
18
+ export declare const signRuntimeNetworkDnsLeaseRenewal: (renewalValue: unknown, authorityValue: unknown, privateKey: plugins.crypto.KeyObject) => Promise<ISignedRuntimeNetworkDnsLeaseRenewal>;
19
+ /**
20
+ * trustedAuthorityValue is receiver-owned persisted trust, NEVER envelope material, and a relay
21
+ * that delivers the renewal adds no trust to it. projectionAuthorityValue is the authority
22
+ * reference that signed the projection this renewal extends, which the receiver kept when it
23
+ * admitted that projection: a renewal is only ever valid under the exact revision that signed its
24
+ * projection, so a rotated key renews nothing and DNS still waits for a newly signed projection.
25
+ * This verifies content, scope and signature only: the owner still admits it against its own
26
+ * admitted projection with `admitRuntimeNetworkDnsLeaseRenewal`, fences the live session and
27
+ * converts the window with a qualified clock.
28
+ */
29
+ export declare const verifyRuntimeNetworkDnsLeaseRenewal: (value: unknown, trustedAuthorityValue: unknown, trustedScopeValue: unknown, projectionAuthorityValue: unknown) => Promise<ISignedRuntimeNetworkDnsLeaseRenewal>;
@@ -1,7 +1,8 @@
1
1
  import * as plugins from './plugins.runtime.js';
2
2
  import { snapshotRuntimeNetworkNodeScopeV1, snapshotRuntimeNetworkProjectionV1, validateRuntimeNetworkProjectionV1 } from './data/networkprojection.js';
3
3
  import { computeRuntimeNetworkSigningAuthorityV1Digest, runtimeNetworkSigningV1Contract, snapshotRuntimeNetworkSigningAuthorityV1, snapshotSignedRuntimeNetworkProjectionV1 } from './data/networksigning.js';
4
- import { failNetwork, networkCanonical, networkEqual, networkReferenceOf, requireNetwork } from './private/networkcontract.js';
4
+ import { runtimeNetworkDnsLeaseRenewalContract, snapshotRuntimeNetworkDnsLeaseRenewal, snapshotSignedRuntimeNetworkDnsLeaseRenewal, validateRuntimeNetworkDnsLeaseRenewal } from './data/networklease.js';
5
+ import { captureNetwork, failNetwork, networkCanonical, networkEqual, networkReference, networkReferenceOf, networkSameScope, requireNetwork } from './private/networkcontract.js';
5
6
  /** The private KeyObject is process-local. Persist only through the controller's encrypted secret store. */
6
7
  export const createRuntimeNetworkSigningKeyV1 = () => {
7
8
  const pair = plugins.crypto.generateKeyPairSync('ed25519');
@@ -9,23 +10,39 @@ export const createRuntimeNetworkSigningKeyV1 = () => {
9
10
  requireNetwork(jwk.kty === 'OKP' && jwk.crv === 'Ed25519' && typeof jwk.x === 'string');
10
11
  return { publicKey: Buffer.from(jwk.x, 'base64url').toString('hex'), privateKey: pair.privateKey };
11
12
  };
12
- const signatureBytes = (authority, projection) => new TextEncoder().encode(networkCanonical({ domain: runtimeNetworkSigningV1Contract.signatureDomain, schemaVersion: 1, authority, projection }));
13
- const validateAuthority = async (authority, projection) => {
14
- requireNetwork(authority.publicKey !== null && networkEqual(authority.controller, projection.controller)
13
+ const projectionEnvelope = (authority, projection) => ({ domain: runtimeNetworkSigningV1Contract.signatureDomain, schemaVersion: 1, authority, projection });
14
+ const renewalEnvelope = (authority, renewal) => ({ domain: runtimeNetworkDnsLeaseRenewalContract.signatureDomain, authority, renewal });
15
+ const envelopeBytes = (envelope) => new TextEncoder().encode(networkCanonical(envelope));
16
+ /** A revoked revision has no key; any other must be intact and belong to the controller that owns the content. */
17
+ const liveKeyOf = async (authority, contentController) => {
18
+ const { publicKey } = authority;
19
+ requireNetwork(publicKey !== null && networkEqual(authority.controller, contentController)
15
20
  && authority.digest === await computeRuntimeNetworkSigningAuthorityV1Digest(authority));
21
+ return publicKey;
22
+ };
23
+ /** Sign as the authority's revision, and only with the private half of its live key. */
24
+ const signAs = async (authority, privateKey, contentController, envelopeOf) => {
25
+ requireNetwork(privateKey instanceof plugins.crypto.KeyObject && privateKey.type === 'private' && privateKey.asymmetricKeyType === 'ed25519');
26
+ const derived = plugins.crypto.createPublicKey(privateKey).export({ format: 'jwk' });
27
+ const publicKey = await liveKeyOf(authority, contentController);
28
+ requireNetwork(typeof derived.x === 'string' && Buffer.from(derived.x, 'base64url').toString('hex') === publicKey);
29
+ const reference = networkReferenceOf(authority);
30
+ return { authority: reference, signature: plugins.crypto.sign(null, envelopeBytes(envelopeOf(reference)), privateKey).toString('hex') };
31
+ };
32
+ /** The receiver's own trusted revision decides; the envelope only names which revision it claims. */
33
+ const requireSignedBy = async (trusted, signed, contentController, envelope) => {
34
+ requireNetwork(networkEqual(signed.authority, networkReferenceOf(trusted)));
35
+ const key = plugins.crypto.createPublicKey({ format: 'jwk', key: { kty: 'OKP', crv: 'Ed25519',
36
+ x: Buffer.from(await liveKeyOf(trusted, contentController), 'hex').toString('base64url') } });
37
+ requireNetwork(plugins.crypto.verify(null, envelopeBytes(envelope), key, Buffer.from(signed.signature, 'hex')));
16
38
  };
17
39
  /** Sign only validated complete state and a matching dedicated private key. */
18
40
  export const signRuntimeNetworkProjectionV1 = async (projectionValue, authorityValue, privateKey) => {
19
41
  try {
20
42
  const input = snapshotRuntimeNetworkProjectionV1(projectionValue), authority = snapshotRuntimeNetworkSigningAuthorityV1(authorityValue);
21
- requireNetwork(privateKey instanceof plugins.crypto.KeyObject && privateKey.type === 'private' && privateKey.asymmetricKeyType === 'ed25519');
22
- const publicKey = plugins.crypto.createPublicKey(privateKey).export({ format: 'jwk' });
23
- requireNetwork(typeof publicKey.x === 'string' && Buffer.from(publicKey.x, 'base64url').toString('hex') === authority.publicKey);
24
43
  const projection = await validateRuntimeNetworkProjectionV1(input);
25
- await validateAuthority(authority, projection);
26
- const reference = networkReferenceOf(authority);
27
- const signature = plugins.crypto.sign(null, signatureBytes(reference, projection), privateKey).toString('hex');
28
- return { schemaVersion: 1, authority: reference, projection, signature };
44
+ const signed = await signAs(authority, privateKey, projection.controller, reference => projectionEnvelope(reference, projection));
45
+ return { schemaVersion: 1, authority: signed.authority, projection, signature: signed.signature };
29
46
  }
30
47
  catch {
31
48
  return failNetwork();
@@ -41,13 +58,8 @@ export const verifyRuntimeNetworkProjectionV1 = async (value, trustedAuthorityVa
41
58
  const signed = snapshotSignedRuntimeNetworkProjectionV1(value);
42
59
  const authority = snapshotRuntimeNetworkSigningAuthorityV1(trustedAuthorityValue);
43
60
  const trusted = snapshotRuntimeNetworkNodeScopeV1(trustedScopeValue);
44
- requireNetwork(networkEqual(signed.authority, networkReferenceOf(authority))
45
- && signed.projection.nodeId === trusted.nodeId && signed.projection.runtimeNamespace === trusted.runtimeNamespace
46
- && networkEqual(signed.projection.controller, trusted.controller));
47
- await validateAuthority(authority, signed.projection);
48
- const key = plugins.crypto.createPublicKey({ format: 'jwk', key: { kty: 'OKP', crv: 'Ed25519',
49
- x: Buffer.from(authority.publicKey, 'hex').toString('base64url') } });
50
- requireNetwork(plugins.crypto.verify(null, signatureBytes(signed.authority, signed.projection), key, Buffer.from(signed.signature, 'hex')));
61
+ requireNetwork(networkSameScope(signed.projection, trusted));
62
+ await requireSignedBy(authority, signed, signed.projection.controller, projectionEnvelope(signed.authority, signed.projection));
51
63
  await validateRuntimeNetworkProjectionV1(signed.projection);
52
64
  return signed;
53
65
  }
@@ -55,4 +67,41 @@ export const verifyRuntimeNetworkProjectionV1 = async (value, trustedAuthorityVa
55
67
  return failNetwork();
56
68
  }
57
69
  };
58
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZS5uZXR3b3JrcHJvamVjdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3J1bnRpbWUubmV0d29ya3Byb2plY3Rpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxzQkFBc0IsQ0FBQztBQUdoRCxPQUFPLEVBQUUsaUNBQWlDLEVBQUUsa0NBQWtDLEVBQUUsa0NBQWtDLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUV4SixPQUFPLEVBQUUsNkNBQTZDLEVBQUUsK0JBQStCLEVBQ3JGLHdDQUF3QyxFQUFFLHdDQUF3QyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDdkgsT0FBTyxFQUFFLFdBQVcsRUFBRSxnQkFBZ0IsRUFBRSxZQUFZLEVBQUUsa0JBQWtCLEVBQUUsY0FBYyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFFL0gsNEdBQTRHO0FBQzVHLE1BQU0sQ0FBQyxNQUFNLGdDQUFnQyxHQUFHLEdBQWdFLEVBQUU7SUFDaEgsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxtQkFBbUIsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMzRCxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQ3JELGNBQWMsQ0FBQyxHQUFHLENBQUMsR0FBRyxLQUFLLEtBQUssSUFBSSxHQUFHLENBQUMsR0FBRyxLQUFLLFNBQVMsSUFBSSxPQUFPLEdBQUcsQ0FBQyxDQUFDLEtBQUssUUFBUSxDQUFDLENBQUM7SUFDeEYsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsV0FBVyxDQUFDLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxFQUFFLFVBQVUsRUFBRSxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7QUFDckcsQ0FBQyxDQUFDO0FB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
70
+ /** Sign only a validated renewal, with the same authority and key that sign this controller's projections. */
71
+ export const signRuntimeNetworkDnsLeaseRenewal = async (renewalValue, authorityValue, privateKey) => {
72
+ try {
73
+ const input = snapshotRuntimeNetworkDnsLeaseRenewal(renewalValue), authority = snapshotRuntimeNetworkSigningAuthorityV1(authorityValue);
74
+ const renewal = await validateRuntimeNetworkDnsLeaseRenewal(input);
75
+ const signed = await signAs(authority, privateKey, renewal.controller, reference => renewalEnvelope(reference, renewal));
76
+ return { authority: signed.authority, renewal, signature: signed.signature };
77
+ }
78
+ catch {
79
+ return failNetwork();
80
+ }
81
+ };
82
+ /**
83
+ * trustedAuthorityValue is receiver-owned persisted trust, NEVER envelope material, and a relay
84
+ * that delivers the renewal adds no trust to it. projectionAuthorityValue is the authority
85
+ * reference that signed the projection this renewal extends, which the receiver kept when it
86
+ * admitted that projection: a renewal is only ever valid under the exact revision that signed its
87
+ * projection, so a rotated key renews nothing and DNS still waits for a newly signed projection.
88
+ * This verifies content, scope and signature only: the owner still admits it against its own
89
+ * admitted projection with `admitRuntimeNetworkDnsLeaseRenewal`, fences the live session and
90
+ * converts the window with a qualified clock.
91
+ */
92
+ export const verifyRuntimeNetworkDnsLeaseRenewal = async (value, trustedAuthorityValue, trustedScopeValue, projectionAuthorityValue) => {
93
+ try {
94
+ const signed = snapshotSignedRuntimeNetworkDnsLeaseRenewal(value);
95
+ const authority = snapshotRuntimeNetworkSigningAuthorityV1(trustedAuthorityValue);
96
+ const trusted = snapshotRuntimeNetworkNodeScopeV1(trustedScopeValue);
97
+ const projectionAuthority = networkReference(captureNetwork(projectionAuthorityValue));
98
+ requireNetwork(networkSameScope(signed.renewal, trusted) && networkEqual(signed.authority, projectionAuthority));
99
+ await requireSignedBy(authority, signed, signed.renewal.controller, renewalEnvelope(signed.authority, signed.renewal));
100
+ await validateRuntimeNetworkDnsLeaseRenewal(signed.renewal);
101
+ return signed;
102
+ }
103
+ catch {
104
+ return failNetwork();
105
+ }
106
+ };
107
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZS5uZXR3b3JrcHJvamVjdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3J1bnRpbWUubmV0d29ya3Byb2plY3Rpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxzQkFBc0IsQ0FBQztBQUdoRCxPQUFPLEVBQUUsaUNBQWlDLEVBQUUsa0NBQWtDLEVBQUUsa0NBQWtDLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUV4SixPQUFPLEVBQUUsNkNBQTZDLEVBQUUsK0JBQStCLEVBQ3JGLHdDQUF3QyxFQUFFLHdDQUF3QyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFdkgsT0FBTyxFQUFFLHFDQUFxQyxFQUFFLHFDQUFxQyxFQUNuRiwyQ0FBMkMsRUFBRSxxQ0FBcUMsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ3JILE9BQU8sRUFBRSxjQUFjLEVBQUUsV0FBVyxFQUFFLGdCQUFnQixFQUFFLFlBQVksRUFBRSxnQkFBZ0IsRUFBRSxrQkFBa0IsRUFDeEcsZ0JBQWdCLEVBQUUsY0FBYyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFFekUsNEdBQTRHO0FBQzVHLE1BQU0sQ0FBQyxNQUFNLGdDQUFnQyxHQUFHLEdBQWdFLEVBQUU7SUFDaEgsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxtQkFBbUIsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMzRCxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQ3JELGNBQWMsQ0FBQyxHQUFHLENBQUMsR0FBRyxLQUFLLEtBQUssSUFBSSxHQUFHLENBQUMsR0FBRyxLQUFLLFNBQVMsSUFBSSxPQUFPLEdBQUcsQ0FBQyxDQUFDLEtBQUssUUFBUSxDQUFDLENBQUM7SUFDeEYsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLENBQUMsSUFBSSxD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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/interfaces",
3
- "version": "31.6.0",
3
+ "version": "31.8.0",
4
4
  "private": false,
5
5
  "description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
6
6
  "exports": {
@@ -31,7 +31,7 @@
31
31
  "@git.zone/tsdoc": "^2.2.0",
32
32
  "@git.zone/tsrun": "^2.0.6",
33
33
  "@git.zone/tstest": "6.1.1",
34
- "@types/node": "26.5.0"
34
+ "@types/node": "26.5.1"
35
35
  },
36
36
  "files": [
37
37
  "ts/**/*",
package/readme.md CHANGED
@@ -371,7 +371,8 @@ set. An empty array declares NODATA; absent/unauthorized names remain NXDOMAIN,
371
371
  and IPv4-only service names have AAAA NODATA. The view carries the existing
372
372
  forwarding intersection and zero or one search suffix. Pallet must prove the
373
373
  actual sandbox/veth/source binding, block private/bare-name forwarding, clip
374
- TTLs and convert the absolute window using its qualified boot/time authority.
374
+ TTLs and convert the effective window from `getRuntimeNetworkEffectiveDnsWindow` (see
375
+ [DNS Lease Renewals](#dns-lease-renewals)) using its qualified boot/time authority.
375
376
  These helpers neither cache queries nor renew a lease.
376
377
 
377
378
  The DNS composer accepts an optional second argument of exact local workload
@@ -454,10 +455,11 @@ capture closed detached JSON shapes and canonical 32-byte key encodings.
454
455
  `bindGetRuntimeManagedVpnCredentialRequestV1(request, projection, trustedSession)`
455
456
  validates the complete projection digest and binds node, controller, namespace,
456
457
  physical-session identity and projection reference. The response binder takes
457
- `(response, request, projection, trustedSession, now)` and additionally requires
458
+ `(response, request, projection, trustedSession, now, renewal = null)` and additionally requires
458
459
  the relay's exact UDP tuple to be selected in `router.platformEndpointIds`.
459
460
  Expiry must be after the caller-qualified time and no later than the referenced
460
- projection's DNS lease; acceptance before that lease starts rejects.
461
+ projection's effective DNS window (see [DNS Lease Renewals](#dns-lease-renewals));
462
+ acceptance before that window starts rejects.
461
463
 
462
464
  These helpers perform content validation. Callers authenticate and fence the
463
465
  current physical peer and projection before and after native work, qualify time
@@ -466,6 +468,132 @@ Noise implementation owns cryptographic validation and authentication. Credentia
466
468
  delivery does not prove a connected VPN, TUN ownership, packet enforcement or
467
469
  workload readiness, and does not replace explicit packet withdrawals.
468
470
 
471
+ ### DNS Lease Renewals
472
+
473
+ A projection's `dns` window lasts at most fifteen minutes and is signed content, so a node's
474
+ private DNS stops when the window ends unless a newer signed statement extends it. Re-issuing the
475
+ projection would extend it too, but a node applies every new projection as new network state,
476
+ which restarts its data plane. `IRuntimeNetworkDnsLeaseRenewal` extends the window and nothing
477
+ else: it carries the node scope (`controller`, `nodeId`, `runtimeNamespace`), the exact
478
+ `projection` reference it renews, a `sequence`, a fresh `dns { issuedAt, notBefore, expiresAt }`
479
+ window and its `digest`. It names the projection by reference, so it never joins the projection
480
+ chain and never changes what the node applied. The new shapes carry no `schemaVersion` and no
481
+ version suffix; the installed package version is the protocol version.
482
+
483
+ `issuedAt` is the start of the window's slot, not the moment of signing: Cloudly signs renewals
484
+ ahead of time on a grid, so the fifteen-minute rule (`maximumDnsLeaseMs`, the same limit a
485
+ projection's window has) is measured from the slot. `computeRuntimeNetworkDnsLeaseRenewalDigest`
486
+ hashes every field except `digest` under its own domain, and `validateRuntimeNetworkDnsLeaseRenewal`
487
+ checks structure and digest only; a digest authenticates nothing.
488
+
489
+ The Node-only `@serve.zone/interfaces/runtime` export signs and verifies renewals with the same
490
+ authority and key that sign the controller's projections, under a separate signature domain.
491
+ `signRuntimeNetworkDnsLeaseRenewal(renewal, authority, privateKey)` produces
492
+ `ISignedRuntimeNetworkDnsLeaseRenewal { authority, renewal, signature }`.
493
+ `verifyRuntimeNetworkDnsLeaseRenewal(signed, trustedAuthority, trustedScope, projectionAuthority)`
494
+ takes the authority and scope from the node's own stored trust, never from the envelope.
495
+ `projectionAuthority` is the reference of the authority revision that signed the projection the
496
+ node admitted, and a renewal verifies only when it was signed under exactly that revision.
497
+ Rotation and revocation end renewals exactly as they end projections: nothing signed under an
498
+ earlier revision verifies after, and a renewal signed under a rotated revision does not renew a
499
+ projection signed under the one before. A renewal never closes a key gap; after rotation, DNS
500
+ continues only with a newly signed projection.
501
+
502
+ `admitRuntimeNetworkDnsLeaseRenewal(renewal, currentProjection, previousRenewal | null, trustedScope, now)`
503
+ is the pure admission decision after verification. The projection and the previous renewal are
504
+ the node's own admitted state, and `now` is its caller-qualified clock, read before the first
505
+ await like every other input. It admits a renewal only when:
506
+
507
+ - the renewal and the projection are in the trusted scope, and the renewal names the current
508
+ projection's exact reference. Once a successor projection is admitted, every renewal of its
509
+ predecessor is refused, and a renewal never introduces a projection the node has not admitted;
510
+ - its `sequence` is higher than the previous renewal's, with gaps allowed, or equal with
511
+ byte-identical content, which is answered as `replay` whatever `now` is, so re-delivery is
512
+ idempotent. The same sequence with any other content is refused;
513
+ - its window is current: `notBefore <= now < expiresAt`. Admitting a window that has not opened
514
+ would displace the coverage the node has now, so a sender delivers a window only once its slot
515
+ has opened; a renewal refused for arriving early is simply delivered again once it has. An
516
+ expired window is refused as well;
517
+ - its window does not move `issuedAt` back and ends later than the previous window. Without a
518
+ previous renewal, the projection's own window is the baseline. Windows need not touch, so a node
519
+ whose lease lapsed can still admit the next renewal it is given.
520
+
521
+ The caller stores the admitted renewal in the same transaction as its trust fence, and deletes it
522
+ in the transaction that admits a successor projection. Admission is not conversion: the node still
523
+ turns the window into a deadline with its qualified clock, and must not use a window before its
524
+ `notBefore`.
525
+
526
+ `requests.runtimesession.IReq_Controller_Pallet_ApplyRuntimeNetworkDnsLeaseRenewal`
527
+ (`applyRuntimeNetworkDnsLeaseRenewal`) delivers one renewal as
528
+ `IApplyRuntimeNetworkDnsLeaseRenewalRequest { session, signed }`.
529
+ `bindApplyRuntimeNetworkDnsLeaseRenewalRequest(request, trustedSession)` requires the node's
530
+ current physical-session binding and a renewal in that session's scope. It does not care who sent
531
+ the request: a cluster relay pushes under the binding the node obtained itself. The answer,
532
+ `IApplyRuntimeNetworkDnsLeaseRenewalResponse { status, renewal }`, repeats
533
+ `IRuntimeNetworkDnsLeaseRenewalReference { projection, sequence, digest }`.
534
+ `bindApplyRuntimeNetworkDnsLeaseRenewalResponse(response, sentRenewal)` compares it with the exact
535
+ renewal that was sent. The ACK asserts durable admission only, not that the node serves DNS.
536
+
537
+ A cluster is outbound-only, so while its relay cannot reach Cloudly nobody can sign a new window.
538
+ Cloudly therefore hands the elected relay a run of renewals it signed ahead of time, with
539
+ `requests.cluster.IReq_Cloudly_Relay_HoldRuntimeNetworkDnsLeaseRenewals`
540
+ (`holdRuntimeNetworkDnsLeaseRenewals`):
541
+ `IHoldRuntimeNetworkDnsLeaseRenewalsRequest { nodeId, projection, renewals }` answered by
542
+ `{ held }`. Each request replaces what the relay held for that node, and an empty run holds
543
+ nothing. The relay keeps the run in memory only, pushes only the renewal whose window is current
544
+ and only while it has no Cloudly session (`escrowRelease`), and holds no key and no trust.
545
+ `snapshotHoldRuntimeNetworkDnsLeaseRenewalsRequest` checks the rules a relay can evaluate without
546
+ the projection itself; the node still measures the first window against its admitted projection,
547
+ and every window against its clock, when it admits it. The run must have:
548
+
549
+ - one node, one projection and one signing authority revision, in one scope;
550
+ - strictly ascending sequences, each window extending the one before;
551
+ - at most `maximumEscrowedRenewals` (32) renewals;
552
+ - at most `maximumEscrowHorizonMs` (four hours) from the first window's `issuedAt` to the last
553
+ window's `expiresAt`. On the intended grid of fifteen-minute windows every ten minutes, the
554
+ horizon allows 23 windows, so it is the limit that binds.
555
+
556
+ `validateHoldRuntimeNetworkDnsLeaseRenewalsRequest` adds every renewal's digest check and
557
+ verifies no signature. `bindHoldRuntimeNetworkDnsLeaseRenewalsResponse(response, sentRequest)`
558
+ requires `held` to equal the number of renewals sent.
559
+
560
+ The horizon is also the revocation latency: a detached cluster keeps DNS authority until its last
561
+ held window ends. So a controller must not release an address or a DNS name before the latest
562
+ `expiresAt` it has signed for that node. Renewals extend DNS only. They grant no packet authority
563
+ and cannot introduce new network state, and they do not extend an already-issued VPN credential;
564
+ a newly issued one may be bound to the renewed window.
565
+
566
+ `getRuntimeNetworkEffectiveDnsWindow(projection, renewal | null)` is the one rule for the window a
567
+ node may use: the admitted renewal's if there is one, else the projection's own. `renewal` is the
568
+ bare `IRuntimeNetworkDnsLeaseRenewal` that admission takes and returns, which is what a node
569
+ stores; Cloudly passes `signed.renewal`. It checks digests and that the renewal names exactly that
570
+ projection, but verifies no signature. Controller and node use it for every limit tied to the
571
+ window, and `bindGetRuntimeManagedVpnCredentialResponseV1` takes the same bare admitted renewal as
572
+ an optional last argument, so a newly issued managed-VPN credential may run to the end of the
573
+ renewed window instead of the projection's original one.
574
+
575
+ ```typescript
576
+ import { data } from '@serve.zone/interfaces';
577
+ import { signRuntimeNetworkDnsLeaseRenewal, verifyRuntimeNetworkDnsLeaseRenewal } from '@serve.zone/interfaces/runtime';
578
+
579
+ const renewal: data.IRuntimeNetworkDnsLeaseRenewal = {
580
+ ...scope,
581
+ projection: { id: projection.id, generation: projection.generation, digest: projection.digest },
582
+ sequence: 1,
583
+ dns: { issuedAt: slotStart, notBefore: slotStart, expiresAt: slotStart + 900_000 },
584
+ digest: `sha256:${'0'.repeat(64)}` as data.TSha256Digest, // well-formed placeholder, replaced below
585
+ };
586
+ renewal.digest = await data.computeRuntimeNetworkDnsLeaseRenewalDigest(renewal);
587
+ const signed = await signRuntimeNetworkDnsLeaseRenewal(renewal, signingAuthority, privateKey);
588
+
589
+ // On the node: stored trust, then the node's own admitted state and qualified clock.
590
+ const verified = await verifyRuntimeNetworkDnsLeaseRenewal(
591
+ signed, storedAuthority, nodeScope, admittedProjectionAuthority);
592
+ const { renewal: admittedRenewal } = await data.admitRuntimeNetworkDnsLeaseRenewal(
593
+ verified.renewal, admittedProjection, previousRenewal, nodeScope, qualifiedNow);
594
+ const window = await data.getRuntimeNetworkEffectiveDnsWindow(admittedProjection, admittedRenewal);
595
+ ```
596
+
469
597
  ### Node-Bound Runtime Assignments
470
598
 
471
599
  The `data` namespace exports `IRuntimeAssignmentV1`, canonical digest helpers,
@@ -1118,6 +1246,8 @@ answers with the block Cloudly published, and `disableClusterRelayV1` removes it
1118
1246
  an explicit `relay: null`. The caller names only the cluster: Cloudly derives the origin itself
1119
1247
  from that cluster and its own public hostname, and refuses the request when no DNS zone it
1120
1248
  manages matches the derived name, so a relay name that nothing can publish is never written.
1249
+ Both requests push the cluster configuration to the connected relay afterwards, so a relay learns
1250
+ the change without reconnecting.
1121
1251
 
1122
1252
  Once its socket is authenticated with `registerCloudlyClientSessionV1`, the relay sends
1123
1253
  `registerClusterRelayV1` carrying `IClusterRelayRegistrationV1`: the relay build, the cluster node
@@ -1828,6 +1958,17 @@ is admitted so putting TLS in front of Corestore needs no contract change. Absen
1828
1958
  runs no Corestore the relay may reach, which an inventory reports as
1829
1959
  `errorCode: 'CORESTORE_ENDPOINT_UNKNOWN'` rather than guessing a hostname.
1830
1960
 
1961
+ An administrator sets it with `setClusterNodeCorestoreEndpoint { identity; nodeId; endpoint }`,
1962
+ answered by Cloudly with the node as it now stands. Cloudly validates the endpoint with
1963
+ `validateClusterNodeCorestore` before it writes, takes the node's own real-write fence inside the
1964
+ writing transaction so a concurrent deletion conflicts instead of leaving an endpoint behind for a
1965
+ node that is gone, and pushes the cluster configuration afterwards so every connected relay learns
1966
+ the change without reconnecting — the same push that
1967
+ [the cluster relay block](#cluster-relay-block) uses when an operator enables or disables a relay.
1968
+ `endpoint: null` clears it, which states the same fact as never having set one. There is no
1969
+ node-wide update request: one field with one validator cannot drift into a generic node mutation
1970
+ whose per-field authority nobody defined.
1971
+
1831
1972
  The endpoint says where, never with what. The bearer control token that authorizes every call is
1832
1973
  obtained from Cloudly through `getCorestoreControlCredentialMaterial`, sealed to the X25519
1833
1974
  recipient the relay enrolled with `beginSecretRecipientEnrollment` and
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '31.6.0',
6
+ version: '31.8.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
package/ts/data/index.ts CHANGED
@@ -27,6 +27,7 @@ export * from './nodeenrollment.js';
27
27
  export * from './networkauthority.js';
28
28
  export * from './networkcredential.js';
29
29
  export * from './networkdelivery.js';
30
+ export * from './networklease.js';
30
31
  export * from './networkprojection.js';
31
32
  export type { IRuntimeNetworkRouterPacketGrantV1, IRuntimeNetworkRouterProjectionV1 } from './networkrouter.js';
32
33
  export * from './networkprotection.js';
@@ -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
+ });