@push.rocks/smartnftables 2.1.0 → 2.2.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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-09-15 - 2.2.0
4
+
5
+ ### Features
6
+
7
+ - Add exact transition release with full boot and namespace binding, terminal retry and joined cleanup, including recovery after policy interfaces disappear.
8
+
3
9
  ## 2026-09-15 - 2.1.0
4
10
 
5
11
  ### Features
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "f87173a416bf8ff6aad2d59dcb8534bbc720325b51a78134ed84c758f1944881",
3
+ "binarySha256": "d997150e9cde38e1ba2c93c5b894c367f9b562a31291ee568ccd1e4a54034b61",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartnftables",
6
- "projectVersion": "2.1.0",
7
- "gitCommit": "59f108c731da9cddfc9622cf5bcd7383ba6b0ecd",
6
+ "projectVersion": "2.2.0",
7
+ "gitCommit": "32e9f40066053a15f32031e796e3151140e98eca",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-09-15T01:41:27.089Z",
9
+ "builtAt": "2026-09-15T06:33:44.972Z",
10
10
  "tsrustVersion": "1.13.0",
11
11
  "binary": "smartnftables",
12
12
  "target": "linux_amd64_musl"
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "206ed227d6c78079f1240f3a70fd2cf305258e84682b2affd97b92fcf3509b5a",
3
+ "binarySha256": "14a625bdb893667872dad6b339aa77f64c6d7e538158bff3e4b92e5684d70e61",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartnftables",
6
- "projectVersion": "2.1.0",
7
- "gitCommit": "59f108c731da9cddfc9622cf5bcd7383ba6b0ecd",
6
+ "projectVersion": "2.2.0",
7
+ "gitCommit": "32e9f40066053a15f32031e796e3151140e98eca",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-09-15T01:41:27.209Z",
9
+ "builtAt": "2026-09-15T06:33:49.548Z",
10
10
  "tsrustVersion": "1.13.0",
11
11
  "binary": "smartnftables",
12
12
  "target": "linux_arm64_musl"
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartnftables',
6
- version: '2.1.0',
6
+ version: '2.2.0',
7
7
  description: 'A TypeScript module for managing nftables rules including NAT, firewall, and rate limiting with a high-level API.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwyQkFBMkI7SUFDakMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLG1IQUFtSDtDQUNqSSxDQUFBIn0=
@@ -1,4 +1,4 @@
1
- import type { IManagedNftOptions, IManagedNftPolicy, IManagedNftTransition, IAppliedManagedNftPolicy, IPreparedManagedNftPolicy, IManagedNftStatus, TManagedNftPolicy } from './managed.types.js';
1
+ import type { IManagedNftOptions, IManagedNftPolicy, IManagedNftTransition, IAppliedManagedNftPolicy, IPreparedManagedNftPolicy, IManagedNftStatus, IManagedNftTransitionRelease, TManagedNftPolicy } from './managed.types.js';
2
2
  export declare class ManagedNftablesError extends Error {
3
3
  readonly code: string;
4
4
  constructor(code: string);
@@ -22,6 +22,14 @@ export declare class ManagedNftables<TPolicy extends TManagedNftPolicy = IManage
22
22
  release(expected: IAppliedManagedNftPolicy<TPolicy>): Promise<{
23
23
  released: true;
24
24
  }>;
25
+ /** Terminal release when an apply acknowledgement was lost. Retain the full
26
+ * original identity and transition first. The native owner accepts only an
27
+ * absent table or the exact previous/target graph; it never recreates policy
28
+ * or requires surviving interfaces. Exact retries and inspection remain
29
+ * available; close() retries admitted cleanup, closeRetaining() only joins. */
30
+ releaseTransition(request: IManagedNftTransitionRelease<TPolicy>): Promise<{
31
+ released: true;
32
+ }>;
25
33
  /** Permanently selects retention cleanup, verifies exact PERSIST retention, and
26
34
  * joins the native process. Retain the full applied value before this call.
27
35
  * After any failure close() only terminates; it never deletes the table. */
@@ -67,6 +67,7 @@ export class ManagedNftables {
67
67
  #retaining = false;
68
68
  #detachExpected = null;
69
69
  #detach = null;
70
+ #transitionRelease = null;
70
71
  #failure = new AbortController();
71
72
  #processLost = false;
72
73
  constructor(options) {
@@ -109,7 +110,7 @@ export class ManagedNftables {
109
110
  cliArgs: ['--management', ...(this.#options.networkNamespaceFd === undefined ? [] : ['--network-namespace-fd', '3'])],
110
111
  inheritedFileDescriptors: this.#options.networkNamespaceFd === undefined ? undefined : [this.#options.networkNamespaceFd],
111
112
  readyTimeoutMs: 3000, requestTimeoutMs: 30_000, maxPayloadSize: 262_144,
112
- maxPendingRequestsByMethod: { preparePolicy: 1, openOwner: 1, reconcilePolicy: 1, inspectPolicy: 1, releasePolicy: 1, detachPolicy: 1, closeOwner: 1 },
113
+ maxPendingRequestsByMethod: { preparePolicy: 1, openOwner: 1, reconcilePolicy: 1, inspectPolicy: 1, releasePolicy: 1, releasePolicyTransition: 1, detachPolicy: 1, closeOwner: 1 },
113
114
  });
114
115
  this.#bridge.on('exit', () => {
115
116
  this.#processLost = true;
@@ -137,11 +138,19 @@ export class ManagedNftables {
137
138
  release(expected) {
138
139
  return this.#invoke('releasePolicy', expected);
139
140
  }
141
+ /** Terminal release when an apply acknowledgement was lost. Retain the full
142
+ * original identity and transition first. The native owner accepts only an
143
+ * absent table or the exact previous/target graph; it never recreates policy
144
+ * or requires surviving interfaces. Exact retries and inspection remain
145
+ * available; close() retries admitted cleanup, closeRetaining() only joins. */
146
+ releaseTransition(request) {
147
+ return this.#invoke('releasePolicyTransition', request);
148
+ }
140
149
  /** Permanently selects retention cleanup, verifies exact PERSIST retention, and
141
150
  * joins the native process. Retain the full applied value before this call.
142
151
  * After any failure close() only terminates; it never deletes the table. */
143
152
  detach(expected) {
144
- if (this.#closeRetainingSelected || this.#closeMode === 'delete')
153
+ if (this.#closeRetainingSelected || this.#closeMode === 'delete' || this.#transitionRelease)
145
154
  return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
146
155
  this.#retaining = true;
147
156
  let captured;
@@ -196,8 +205,13 @@ export class ManagedNftables {
196
205
  await this.#work?.catch(() => undefined);
197
206
  if (this.#bridge) {
198
207
  if (this.#nativeMayOwn && !retaining) {
199
- const result = await this.#bridge.sendCommand('closeOwner', {});
200
- if (result.released !== true)
208
+ // Even native validation may have rejected before selecting its own
209
+ // terminal intent. Retry this exact request; generic close must not
210
+ // delete an earlier applied graph outside the requested transition.
211
+ const result = this.#transitionRelease
212
+ ? await this.#bridge.sendCommand('releasePolicyTransition', this.#transitionRelease.request)
213
+ : await this.#bridge.sendCommand('closeOwner', {});
214
+ if (result.released !== true || this.#transitionRelease && Object.keys(result).length !== 1)
201
215
  throw new ManagedNftablesError('PROTOCOL');
202
216
  }
203
217
  await this.#bridge.terminate({ gracePeriodMs: 1000 });
@@ -222,18 +236,28 @@ export class ManagedNftables {
222
236
  catch {
223
237
  return Promise.reject(new ManagedNftablesError('INVALID'));
224
238
  }
225
- if (method === 'reconcilePolicy' || method === 'releasePolicy')
239
+ if (this.#transitionRelease && (method !== 'inspectPolicy' && method !== 'releasePolicyTransition'
240
+ || method === 'releasePolicyTransition' && !plugins.util.isDeepStrictEqual(this.#transitionRelease.request, captured))) {
241
+ return Promise.reject(new ManagedNftablesError('CONFLICT'));
242
+ }
243
+ if (method === 'releasePolicyTransition')
244
+ this.#transitionRelease = { request: captured };
245
+ if (method === 'reconcilePolicy' || method === 'releasePolicy' || method === 'releasePolicyTransition')
226
246
  this.#nativeMayOwn = true;
227
247
  return this.#track(this.#bridge.sendCommand(method, captured).then((result) => {
228
248
  const capturedResult = snapshot(result);
229
- if (method === 'releasePolicy')
249
+ if (method === 'releasePolicy' || method === 'releasePolicyTransition') {
250
+ if (method === 'releasePolicyTransition' && (capturedResult?.released !== true
251
+ || Object.keys(capturedResult).length !== 1))
252
+ throw new ManagedNftablesError('PROTOCOL');
230
253
  this.#nativeMayOwn = false;
254
+ }
231
255
  if (method === 'inspectPolicy') {
232
256
  const status = capturedResult;
233
257
  if (status.state === 'failed-owned' || status.error !== null)
234
258
  this.#fail();
235
259
  }
236
- if (!this.#processLost && this.#state !== 'closing' && (method === 'reconcilePolicy' || method === 'releasePolicy'
260
+ if (!this.#processLost && this.#state !== 'closing' && (method === 'reconcilePolicy' || method === 'releasePolicy' || method === 'releasePolicyTransition'
237
261
  || method === 'inspectPolicy' && capturedResult.error === null
238
262
  && capturedResult.state !== 'failed-owned'))
239
263
  this.#state = 'ready';
@@ -252,7 +276,7 @@ export class ManagedNftables {
252
276
  && error.code === 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE'
253
277
  && (method === 'preparePolicy' || code === 'INVALID');
254
278
  if (!validation && (this.#state !== 'closing'
255
- || ['reconcilePolicy', 'releasePolicy', 'detachPolicy'].includes(method)))
279
+ || ['reconcilePolicy', 'releasePolicy', 'releasePolicyTransition', 'detachPolicy'].includes(method)))
256
280
  this.#fail();
257
281
  throw new ManagedNftablesError(typeof code === 'string' && /^[A-Z_]{1,64}$/.test(code) ? code : 'UNAVAILABLE');
258
282
  });
@@ -261,4 +285,4 @@ export class ManagedNftables {
261
285
  return owned;
262
286
  }
263
287
  }
264
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tYW5hZ2VkbmZ0YWJsZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLm1hbmFnZWRuZnRhYmxlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUl4QyxNQUFNLE9BQU8sb0JBQXFCLFNBQVEsS0FBSztJQUM3QyxZQUE0QixJQUFZO1FBQUksS0FBSyxDQUFDLG9CQUFvQixJQUFJLEdBQUcsQ0FBQyxDQUFDO1FBQW5ELFNBQUksR0FBSixJQUFJLENBQVE7SUFBd0MsQ0FBQztDQUNsRjtBQUVELG9GQUFvRjtBQUNwRixNQUFNLFVBQVUsUUFBUSxDQUFJLEtBQVE7SUFDbEMsSUFBSSxLQUFLLEdBQUcsQ0FBQyxDQUFDO0lBQ2QsSUFBSSxXQUFXLEdBQUcsQ0FBQyxDQUFDO0lBQ3BCLE1BQU0sTUFBTSxHQUFHLENBQUMsS0FBYSxFQUFVLEVBQUU7UUFDdkMsSUFBSSxLQUFLLENBQUMsTUFBTSxHQUFHLE9BQU8sSUFBSSxDQUFDLFdBQVcsSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsT0FBTztZQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUM3SCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUMsQ0FBQztJQUNGLE1BQU0sSUFBSSxHQUFHLENBQUMsS0FBYyxFQUFFLEtBQWEsRUFBVyxFQUFFO1FBQ3RELElBQUksRUFBRSxLQUFLLEdBQUcsTUFBTSxJQUFJLEtBQUssR0FBRyxFQUFFO1lBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzlFLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUTtZQUFFLE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3BELElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxPQUFPLEtBQUssS0FBSyxTQUFTO1lBQUUsT0FBTyxLQUFLLENBQUM7UUFDL0QsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksTUFBTSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUM7WUFBRSxPQUFPLEtBQUssQ0FBQztRQUMzRSxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDO1lBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzlHLE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDbkMsSUFBSSxNQUFNLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDO1lBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzdILE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDcEMsSUFBSSxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sR0FBRyxLQUFLO1lBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzVFLEtBQUssTUFBTSxHQUFHLElBQUksSUFBSTtZQUFFLElBQUksT0FBTyxHQUFHLEtBQUssUUFBUTtnQkFBRSxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDakUsTUFBTSxXQUFXLEdBQUcsTUFBTSxDQUFDLHlCQUF5QixDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzVELE1BQU0sTUFBTSxHQUF3QyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQ3BFLElBQUksS0FBSyxJQUFJLElBQUksQ0FBQyxNQUFNLEtBQU0sS0FBbUIsQ0FBQyxNQUFNLEdBQUcsQ0FBQztZQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUN4RyxLQUFLLE1BQU0sR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDO1lBQ3ZCLElBQUksS0FBSyxJQUFJLEdBQUcsS0FBSyxRQUFRO2dCQUFFLFNBQVM7WUFDeEMsTUFBTSxVQUFVLEdBQUcsV0FBVyxDQUFDLEdBQWEsQ0FBQyxDQUFDO1lBQzlDLElBQUksT0FBTyxHQUFHLEtBQUssUUFBUSxJQUFJLENBQUMsVUFBVSxDQUFDLFVBQVUsSUFBSSxDQUFDLENBQUMsT0FBTyxJQUFJLFVBQVUsQ0FBQzttQkFDNUUsS0FBSyxJQUFJLENBQUMsbUJBQW1CLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQztnQkFBRSxNQUFNLElBQUksb0JBQW9CLENBQUMsU0FBUyxDQUFDLENBQUM7WUFDeEYsTUFBTSxDQUFDLGNBQWMsQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFLEVBQUUsS0FBSyxFQUFFLElBQUksQ0FBQyxVQUFVLENBQUMsS0FBSyxFQUFFLEtBQUssR0FBRyxDQUFDLENBQUMsRUFBRSxVQUFVLEVBQUUsSUFBSSxFQUFFLFlBQVksRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7UUFDekksQ0FBQztRQUNELE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUMsQ0FBQztJQUNGLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFNLENBQUM7SUFDbkMsSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUMsR0FBRyxPQUFPO1FBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ25HLE9BQU8sTUFBTSxDQUFDO0FBQ2hCLENBQUM7QUFFRCxzR0FBc0c7QUFDdEcsTUFBTSxPQUFPLGVBQWU7SUFDakIsUUFBUSxDQUErQjtJQUNoRCxPQUFPLEdBQXNFLElBQUksQ0FBQztJQUNsRixNQUFNLEdBQTBFLEtBQUssQ0FBQztJQUN0RixLQUFLLEdBQTRCLElBQUksQ0FBQztJQUN0QyxNQUFNLEdBQXlCLElBQUksQ0FBQztJQUNwQyxVQUFVLEdBQStCLElBQUksQ0FBQztJQUM5Qyx1QkFBdUIsR0FBRyxLQUFLLENBQUM7SUFDaEMsYUFBYSxHQUFHLEtBQUssQ0FBQztJQUN0QixVQUFVLEdBQUcsS0FBSyxDQUFDO0lBQ25CLGVBQWUsR0FBNkMsSUFBSSxDQUFDO0lBQ2pFLE9BQU8sR0FBdUMsSUFBSSxDQUFDO0lBQzFDLFFBQVEsR0FBRyxJQUFJLGVBQWUsRUFBRSxDQUFDO0lBQzFDLFlBQVksR0FBRyxLQUFLLENBQUM7SUFFckIsWUFBWSxPQUEyQjtRQUNyQyxJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNsQyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLFNBQVMsRUFBRSxZQUFZLEVBQUUsV0FBVyxFQUFFLFlBQVksRUFBRSxvQkFBb0IsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQztlQUNsSSxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxTQUFTLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7ZUFDOUosSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsTUFBTSxHQUFHLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUM7ZUFDcEYsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLEtBQUssU0FBUyxJQUFJLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDL0ksTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzVDLENBQUM7UUFDRCxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsa0JBQWtCLEtBQUssU0FBUyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsS0FBSyxPQUFPO2VBQzlFLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLGtCQUFrQixDQUFDLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxrQkFBa0IsR0FBRyxDQUFDO2VBQzNGLElBQUksQ0FBQyxRQUFRLENBQUMsa0JBQWtCLEdBQUcsV0FBVyxDQUFDO1lBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ2xHLENBQUM7SUFFRCxJQUFXLEtBQUssS0FBYSxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBRWxEOzs4RUFFMEU7SUFDMUUsSUFBVyxhQUFhLEtBQWtCLE9BQU8sSUFBSSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBRXhFLEtBQUs7UUFDSCxJQUFJLElBQUksQ0FBQyxNQUFNLEtBQUssU0FBUztZQUFFLElBQUksQ0FBQyxNQUFNLEdBQUcsY0FBYyxDQUFDO1FBQzVELElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxPQUFPO1lBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsSUFBSSxvQkFBb0IsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDO0lBQ2pHLENBQUM7SUFFRCx1RkFBdUY7SUFDaEYsS0FBSztRQUNWLElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxLQUFLLElBQUksSUFBSSxDQUFDLFVBQVU7WUFBRSxPQUFPLE9BQU8sQ0FBQyxNQUFNLENBQUMsSUFBSSxvQkFBb0IsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDO1FBQzdHLElBQUksQ0FBQyxNQUFNLEdBQUcsVUFBVSxDQUFDO1FBQ3pCLE1BQU0sSUFBSSxHQUFHLENBQUMsS0FBSyxJQUFJLEVBQUU7WUFDdkIsSUFBSSxPQUFPLENBQUMsUUFBUSxLQUFLLE9BQU8sSUFBSSxDQUFDLENBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDO2dCQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1lBQ3JJLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7WUFDeEUsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLE9BQU8sQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUErQjtnQkFDNUUsVUFBVSxFQUFFLGVBQWUsRUFBRSxVQUFVLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVO3VCQUM1RCxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsV0FBVyxFQUFFLHVCQUF1QixPQUFPLENBQUMsSUFBSSxLQUFLLEtBQUssQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxPQUFPLE9BQU8sQ0FBQztnQkFDbkgsVUFBVSxFQUFFLEVBQUUsRUFBRSxnQkFBZ0IsRUFBRSxLQUFLO2dCQUN2QyxPQUFPLEVBQUUsQ0FBQyxjQUFjLEVBQUUsR0FBRyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsa0JBQWtCLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsd0JBQXdCLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztnQkFDckgsd0JBQXdCLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxrQkFBa0IsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLGtCQUFrQixDQUFDO2dCQUN6SCxjQUFjLEVBQUUsSUFBSSxFQUFFLGdCQUFnQixFQUFFLE1BQU0sRUFBRSxjQUFjLEVBQUUsT0FBTztnQkFDdkUsMEJBQTBCLEVBQUUsRUFBRSxhQUFhLEVBQUUsQ0FBQyxFQUFFLFNBQVMsRUFBRSxDQUFDLEVBQUUsZUFBZSxFQUFFLENBQUMsRUFBRSxhQUFhLEVBQUUsQ0FBQyxFQUFFLGFBQWEsRUFBRSxDQUFDLEVBQUUsWUFBWSxFQUFFLENBQUMsRUFBRSxVQUFVLEVBQUUsQ0FBQyxFQUFFO2FBQ3ZKLENBQUMsQ0FBQztZQUNILElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUU7Z0JBQzNCLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDO2dCQUN6QixJQUFJLENBQUMsQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7b0JBQUUsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ2xFLENBQUMsQ0FBQyxDQUFDO1lBQ0gsSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUU7Z0JBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLGFBQWEsQ0FBQyxDQUFDO1lBQy9FLE1BQU0sRUFBRSxPQUFPLEVBQUUsVUFBVSxFQUFFLFNBQVMsRUFBRSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUM7WUFDekQsTUFBTSxNQUFNLEdBQUcsUUFBUSxDQUFDLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsV0FBVyxFQUFFLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDLENBQUM7WUFDekcsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLFVBQVU7Z0JBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLGFBQWEsQ0FBQyxDQUFDO1lBQzlFLElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDO1lBQUMsT0FBTyxNQUFNLENBQUM7UUFDdkMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUNMLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsV0FBVyxDQUFDLENBQUM7SUFDeEMsQ0FBQztJQUVNLE9BQU8sQ0FBQyxNQUFlO1FBQzVCLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxlQUFlLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDL0MsQ0FBQztJQUNNLFNBQVMsQ0FBQyxVQUEwQztRQUN6RCxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsaUJBQWlCLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDckQsQ0FBQztJQUNNLE9BQU8sS0FBMEMsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLGVBQWUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDNUYsT0FBTyxDQUFDLFFBQTJDO1FBQ3hELE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxlQUFlLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDakQsQ0FBQztJQUVEOztnRkFFNEU7SUFDckUsTUFBTSxDQUFDLFFBQTJDO1FBQ3ZELElBQUksSUFBSSxDQUFDLHVCQUF1QixJQUFJLElBQUksQ0FBQyxVQUFVLEtBQUssUUFBUTtZQUFFLE9BQU8sT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLG9CQUFvQixDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUM7UUFDakksSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLENBQUM7UUFDdkIsSUFBSSxRQUEyQyxDQUFDO1FBQ2hELElBQUksQ0FBQztZQUNILFFBQVEsR0FBRyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDOUIsSUFBSSxJQUFJLENBQUMsZUFBZSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsZUFBZSxFQUFFLFFBQVEsQ0FBQztnQkFBRSxNQUFNLElBQUksb0JBQW9CLENBQUMsVUFBVSxDQUFDLENBQUM7WUFDeEksSUFBSSxJQUFJLENBQUMsT0FBTztnQkFBRSxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUM7WUFDdEMsSUFBSSxDQUFDLENBQUMsT0FBTyxFQUFFLGNBQWMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLElBQUksQ0FBQyxLQUFLLElBQUksSUFBSSxDQUFDLE1BQU07Z0JBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLGFBQWEsQ0FBQyxDQUFDO1lBQ2xKLElBQUksQ0FBQyxlQUFlLEdBQUcsUUFBUSxDQUFDO1FBQ2xDLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQUMsT0FBTyxPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssWUFBWSxvQkFBb0IsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7UUFBQyxDQUFDO1FBQy9ILE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsY0FBYyxFQUFFLFFBQVEsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFO1lBQ3pGLE1BQU0sTUFBTSxHQUFHLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUMvQixJQUFJLE1BQU0sRUFBRSxRQUFRLEtBQUssSUFBSSxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxLQUFLLENBQUM7Z0JBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFVBQVUsQ0FBQyxDQUFDO1lBQzlHLElBQUksQ0FBQyxhQUFhLEdBQUcsS0FBSyxDQUFDO1lBQzNCLE9BQU8sTUFBTSxDQUFDO1FBQ2hCLENBQUMsQ0FBQyxFQUFFLGNBQWMsQ0FBQyxDQUFDO1FBQ3BCLE1BQU0sUUFBUSxHQUFHLENBQUMsS0FBSyxJQUFJLEVBQUUsR0FBRyxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxDQUFDLE1BQU0sSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUMsT0FBTyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQ25HLElBQUksQ0FBQyxPQUFPLEdBQUcsUUFBUSxDQUFDO1FBQ3hCLEtBQUssUUFBUSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3BELE9BQU8sUUFBUSxDQUFDO0lBQ2xCLENBQUM7SUFFRDs7OzsyRUFJdUU7SUFDaEUsY0FBYztRQUNuQixJQUFJLElBQUksQ0FBQyxVQUFVLEtBQUssUUFBUTtZQUFFLE9BQU8sT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLG9CQUFvQixDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUM7UUFDOUYsSUFBSSxDQUFDLHVCQUF1QixHQUFHLElBQUksQ0FBQztRQUNwQyxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQztRQUN2QixPQUFPLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUN0QixDQUFDO0lBRUQ7bUdBQytGO0lBQ3hGLEtBQUs7UUFDVixJQUFJLElBQUksQ0FBQyxNQUFNO1lBQUUsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQ3BDLElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxTQUFTO1lBQUUsT0FBTyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDeEQsSUFBSSxDQUFDLFVBQVUsS0FBSyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQztRQUMxRCxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsVUFBVSxLQUFLLFFBQVEsQ0FBQztRQUMvQyxJQUFJLENBQUMsTUFBTSxHQUFHLFNBQVMsQ0FBQztRQUN4QixJQUFJLENBQUMsTUFBTSxHQUFHLENBQUMsS0FBSyxJQUFJLEVBQUU7WUFDeEIsTUFBTSxJQUFJLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxHQUFHLEVBQUUsQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUN6QyxJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFDakIsSUFBSSxJQUFJLENBQUMsYUFBYSxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7b0JBQ3JDLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsWUFBWSxFQUFFLEVBQUUsQ0FBQyxDQUFDO29CQUNoRSxJQUFJLE1BQU0sQ0FBQyxRQUFRLEtBQUssSUFBSTt3QkFBRSxNQUFNLElBQUksb0JBQW9CLENBQUMsVUFBVSxDQUFDLENBQUM7Z0JBQzNFLENBQUM7Z0JBQ0QsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxFQUFFLGFBQWEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO1lBQ3hELENBQUM7WUFDRCxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQztZQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDO1FBQy9DLENBQUMsQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRTtZQUNkLElBQUksQ0FBQyxNQUFNLEdBQUcsY0FBYyxDQUFDO1lBQUMsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUM7WUFDakQsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ2IsTUFBTSxJQUFJLG9CQUFvQixDQUFDLHFCQUFxQixDQUFDLENBQUM7UUFDeEQsQ0FBQyxDQUFDLENBQUM7UUFDSCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUM7SUFDckIsQ0FBQztJQUVELE9BQU8sQ0FBK0MsTUFBUyxFQUFFLE1BQWlEO1FBQ2hILElBQUksSUFBSSxDQUFDLFVBQVUsSUFBSSxDQUFDLENBQUMsT0FBTyxFQUFFLGNBQWMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLElBQUksQ0FBQyxLQUFLO1lBQUUsT0FBTyxPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksb0JBQW9CLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQztRQUN2SyxJQUFJLFFBQW1ELENBQUM7UUFDeEQsSUFBSSxDQUFDO1lBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUFDLENBQUM7UUFBQyxNQUFNLENBQUM7WUFBQyxPQUFPLE9BQU8sQ0FBQyxNQUFNLENBQUMsSUFBSSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO1FBQUMsQ0FBQztRQUMxRyxJQUFJLE1BQU0sS0FBSyxpQkFBaUIsSUFBSSxNQUFNLEtBQUssZUFBZTtZQUFFLElBQUksQ0FBQyxhQUFhLEdBQUcsSUFBSSxDQUFDO1FBQzFGLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxNQUFNLEVBQUUsUUFBUSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUU7WUFDNUUsTUFBTSxjQUFjLEdBQUcsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQ3hDLElBQUksTUFBTSxLQUFLLGVBQWU7Z0JBQUUsSUFBSSxDQUFDLGFBQWEsR0FBRyxLQUFLLENBQUM7WUFDM0QsSUFBSSxNQUFNLEtBQUssZUFBZSxFQUFFLENBQUM7Z0JBQy9CLE1BQU0sTUFBTSxHQUFHLGNBQTRDLENBQUM7Z0JBQzVELElBQUksTUFBTSxDQUFDLEtBQUssS0FBSyxjQUFjLElBQUksTUFBTSxDQUFDLEtBQUssS0FBSyxJQUFJO29CQUFFLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUM3RSxDQUFDO1lBQ0QsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxTQUFTLElBQUksQ0FBQyxNQUFNLEtBQUssaUJBQWlCLElBQUksTUFBTSxLQUFLLGVBQWU7bUJBQzdHLE1BQU0sS0FBSyxlQUFlLElBQUssY0FBNkMsQ0FBQyxLQUFLLEtBQUssSUFBSTt1QkFDeEYsY0FBNkMsQ0FBQyxLQUFLLEtBQUssY0FBYyxDQUFDO2dCQUFFLElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDO1lBQ3ZHLE9BQU8sY0FBYyxDQUFDO1FBQ3hCLENBQUMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQ2QsQ0FBQztJQUNELE1BQU0sQ0FBSSxJQUFnQixFQUFFLE1BQTBDO1FBQ3BFLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRTtZQUNqQyxJQUFJLElBQUksQ0FBQyxNQUFNLEtBQUssU0FBUztnQkFBRSxJQUFJLENBQUMsTUFBTSxHQUFHLGNBQWMsQ0FBQztZQUM1RCxNQUFNLElBQUksR0FBRyxLQUFLLFlBQVksT0FBTyxDQUFDLFNBQVMsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLGlCQUFpQixDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7WUFDN0csdUVBQXVFO1lBQ3ZFLHdFQUF3RTtZQUN4RSx5RUFBeUU7WUFDekUsTUFBTSxVQUFVLEdBQUcsS0FBSyxZQUFZLE9BQU8sQ0FBQyxTQUFTLENBQUMsc0JBQXNCO21CQUN2RSxLQUFLLENBQUMsSUFBSSxLQUFLLHVDQUF1QzttQkFDdEQsQ0FBQyxNQUFNLEtBQUssZUFBZSxJQUFJLElBQUksS0FBSyxTQUFTLENBQUMsQ0FBQztZQUN4RCxJQUFJLENBQUMsVUFBVSxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sS0FBSyxTQUFTO21CQUN4QyxDQUFDLGlCQUFpQixFQUFFLGVBQWUsRUFBRSxjQUFjLENBQUMsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7Z0JBQUUsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQzFGLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxPQUFPLElBQUksS0FBSyxRQUFRLElBQUksZ0JBQWdCLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQ2pILENBQUMsQ0FBQyxDQUFDO1FBQ0gsSUFBSSxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7UUFDbkIsS0FBSyxLQUFLLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxLQUFLLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxLQUFLLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDNUUsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0NBQ0YifQ==
288
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tYW5hZ2VkbmZ0YWJsZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLm1hbmFnZWRuZnRhYmxlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUl4QyxNQUFNLE9BQU8sb0JBQXFCLFNBQVEsS0FBSztJQUM3QyxZQUE0QixJQUFZO1FBQUksS0FBSyxDQUFDLG9CQUFvQixJQUFJLEdBQUcsQ0FBQyxDQUFDO1FBQW5ELFNBQUksR0FBSixJQUFJLENBQVE7SUFBd0MsQ0FBQztDQUNsRjtBQUVELG9GQUFvRjtBQUNwRixNQUFNLFVBQVUsUUFBUSxDQUFJLEtBQVE7SUFDbEMsSUFBSSxLQUFLLEdBQUcsQ0FBQyxDQUFDO0lBQ2QsSUFBSSxXQUFXLEdBQUcsQ0FBQyxDQUFDO0lBQ3BCLE1BQU0sTUFBTSxHQUFHLENBQUMsS0FBYSxFQUFVLEVBQUU7UUFDdkMsSUFBSSxLQUFLLENBQUMsTUFBTSxHQUFHLE9BQU8sSUFBSSxDQUFDLFdBQVcsSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsT0FBTztZQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUM3SCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUMsQ0FBQztJQUNGLE1BQU0sSUFBSSxHQUFHLENBQUMsS0FBYyxFQUFFLEtBQWEsRUFBVyxFQUFFO1FBQ3RELElBQUksRUFBRSxLQUFLLEdBQUcsTUFBTSxJQUFJLEtBQUssR0FBRyxFQUFFO1lBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzlFLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUTtZQUFFLE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3BELElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxPQUFPLEtBQUssS0FBSyxTQUFTO1lBQUUsT0FBTyxL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
@@ -51,6 +51,13 @@ export interface IManagedNftTransition<TPolicy extends TManagedNftPolicy = IMana
51
51
  previous: IAppliedManagedNftPolicy<TPolicy> | null;
52
52
  target: IPreparedManagedNftPolicy<TPolicy>;
53
53
  }
54
+ /** Persist the original boot/namespace identity and full transition before cleanup.
55
+ * This releases only its exact owned table; it does not prove packet drain or
56
+ * authorize address reuse. Surviving policy interfaces are not required. */
57
+ export interface IManagedNftTransitionRelease<TPolicy extends TManagedNftPolicy = IManagedNftPolicy> {
58
+ identity: IManagedNftIdentity;
59
+ transition: IManagedNftTransition<TPolicy>;
60
+ }
54
61
  export interface IManagedNftStatus<TPolicy extends TManagedNftPolicy = IManagedNftPolicy> {
55
62
  identity: IManagedNftIdentity;
56
63
  state: 'idle' | 'applied' | 'failed-owned' | 'released' | 'retained';
@@ -96,6 +103,12 @@ export type TManagedNftCommands<TPolicy extends TManagedNftPolicy = IManagedNftP
96
103
  released: true;
97
104
  };
98
105
  };
106
+ releasePolicyTransition: {
107
+ params: IManagedNftTransitionRelease<TPolicy>;
108
+ result: {
109
+ released: true;
110
+ };
111
+ };
99
112
  detachPolicy: {
100
113
  params: IAppliedManagedNftPolicy<TPolicy>;
101
114
  result: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartnftables",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "private": false,
5
5
  "description": "A TypeScript module for managing nftables rules including NAT, firewall, and rate limiting with a high-level API.",
6
6
  "main": "dist_ts/index.js",
package/readme.md CHANGED
@@ -114,6 +114,7 @@ grants remain outstanding.
114
114
  | `reconcile({ previous, target })` | Creates or replaces the entire policy in one kernel transaction. `previous` is null initially, otherwise the full prior applied result. |
115
115
  | `inspect()` | Reads the current owned graph and reports `enforced`, pending intent and a bounded error code. |
116
116
  | `release(applied)` | Verifies the exact retained graph and confirms deletion; repeats are idempotent. |
117
+ | `releaseTransition({ identity, transition })` | Selects terminal cleanup using the original full identity and transition when the applied result is unavailable. Accepts absence or deletes the exact previous/target graph without requiring surviving interfaces. |
117
118
  | `detach(applied)` | Selects terminal retention, verifies the same graph with PERSIST-only flags after dropping socket ownership, then joins the native process. Returns `{ detached: true }`; retain the full applied result. |
118
119
  | `closeRetaining()` | Stops command admission, joins admitted operations and terminates the native process without requesting deletion or confirming retention. Use when the exact applied result is unavailable. |
119
120
  | `close()` | Stops command admission, joins admitted policy operations, confirms owned-table deletion unless retention was selected, then joins the native process. Failure retains the owner for explicit recovery. |
@@ -202,6 +203,35 @@ may have left no graph; a lost reply may have left the original or target graph.
202
203
  Keep the complete original transition durably and recover it through a fresh owner
203
204
  with identical options. Never fabricate an applied receipt to request detachment.
204
205
 
206
+ When cleanup is required instead of recovering enforcement, persist a
207
+ `IManagedNftTransitionRelease` containing the identity returned by the original
208
+ `start()` and the full original transition. After fencing workload packet
209
+ admission and joining the old owner, start a fresh owner with the same options:
210
+
211
+ ```typescript
212
+ // Both values come from the caller's durable journal, before the lost apply ACK.
213
+ const cleanup = { identity: originalIdentity, transition: originalTransition };
214
+ await recoveryOwner.releaseTransition(cleanup);
215
+ await recoveryOwner.close();
216
+ ```
217
+
218
+ `releaseTransition()` validates the original boot and namespace, canonical target,
219
+ complete previous result when present, table identity and entire rule graph. It
220
+ accepts an absent table, the previous graph, or the target graph; it never creates
221
+ or replaces policy. Deleted or changed interfaces do not prevent this cleanup.
222
+ Orphan adoption checks the graph before and after claiming OWNER, binds the prior
223
+ or already-observed handle, and rejects another live owner. Deletion uses the exact
224
+ handle and a generation-fenced batch, followed by an absence check.
225
+
226
+ Once admitted, only that captured cleanup body may be retried. Other policy writes,
227
+ preparation and detachment reject; inspection never claims enforcement. An ordinary
228
+ `close()` retries the selected cleanup before joining the child. `closeRetaining()`
229
+ only joins, allowing a fresh process to retry the durably retained cleanup after
230
+ an ambiguous acknowledgement. Local inert-input or busy rejection does not select
231
+ cleanup. A successful release acknowledges that table operation at observation
232
+ time, including an exact retry after a lost delete ACK. It does not prove packet
233
+ drain, clear conntrack, release allocations, or cross a changed boot/namespace.
234
+
205
235
  After `closeRetaining()`, all policy commands, including `detach()`, reject.
206
236
  Concurrent cleanup calls share the same join. A failed join keeps the native
207
237
  owner and retention choice for retry through `closeRetaining()` or `close()`.
package/rust/src/main.rs CHANGED
@@ -91,6 +91,10 @@ fn dispatch(owner: &mut Option<owner::Owner>, request: Request) -> Result<serde_
91
91
  .release(decode(request.params)?)?;
92
92
  Ok(serde_json::json!({"released":true}))
93
93
  }
94
+ "releasePolicyTransition" => {
95
+ owner.as_mut().ok_or(Error::Conflict)?.release_transition(decode(request.params)?)?;
96
+ Ok(serde_json::json!({"released":true}))
97
+ }
94
98
  "detachPolicy" => {
95
99
  owner.as_mut().ok_or(Error::Conflict)?.detach(decode(request.params)?)?;
96
100
  Ok(serde_json::json!({"detached":true}))
@@ -5,7 +5,7 @@ impl Owner {
5
5
  /// orphan after an ACK was lost, including through a fresh native process.
6
6
  pub fn detach(&mut self, expected: Applied) -> Result<()> {
7
7
  self.validate_receipt(&expected)?;
8
- if self.released || self.pending.is_some() {
8
+ if self.released || self.pending.is_some() || self.transition_release.is_some() {
9
9
  return Err(Error::Conflict);
10
10
  }
11
11
  if let Some(retention) = &self.retention {
package/rust/src/owner.rs CHANGED
@@ -44,6 +44,12 @@ pub struct Transition {
44
44
  pub previous: Option<Applied>,
45
45
  pub target: Prepared,
46
46
  }
47
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
48
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
49
+ pub struct TransitionRelease {
50
+ pub identity: Identity,
51
+ pub transition: Transition,
52
+ }
47
53
  #[derive(Clone, Debug, Serialize)]
48
54
  #[serde(rename_all = "camelCase")]
49
55
  pub struct Status {
@@ -69,6 +75,7 @@ pub struct Owner {
69
75
  error: Option<Error>,
70
76
  released: bool,
71
77
  retention: Option<Retention>,
78
+ transition_release: Option<TransitionRelease>,
72
79
  }
73
80
 
74
81
  struct Retention {
@@ -78,6 +85,8 @@ struct Retention {
78
85
 
79
86
  #[path = "owner.detach.rs"]
80
87
  mod detach;
88
+ #[path = "owner.transitionrelease.rs"]
89
+ mod transitionrelease;
81
90
 
82
91
  pub(crate) fn context() -> Result<(String, String, String)> {
83
92
  let boot = std::fs::read_to_string("/proc/sys/kernel/random/boot_id")
@@ -164,6 +173,7 @@ impl Owner {
164
173
  error: None,
165
174
  released: false,
166
175
  retention: None,
176
+ transition_release: None,
167
177
  })
168
178
  }
169
179
  fn socket(&mut self) -> Result<&mut Socket> {
@@ -360,7 +370,7 @@ impl Owner {
360
370
  })
361
371
  }
362
372
  pub fn reconcile(&mut self, transition: Transition) -> Result<Applied> {
363
- if self.released || self.retention.is_some() {
373
+ if self.released || self.retention.is_some() || self.transition_release.is_some() {
364
374
  return Err(Error::Conflict);
365
375
  }
366
376
  transition.target.validate()?;
@@ -463,6 +473,9 @@ impl Owner {
463
473
  self.applied(&graph, transition.target.clone())
464
474
  }
465
475
  pub fn inspect(&mut self) -> Status {
476
+ if self.transition_release.is_some() {
477
+ return self.inspect_transition_release();
478
+ }
466
479
  if self.retention.is_some() {
467
480
  return self.inspect_retained();
468
481
  }
@@ -503,7 +516,7 @@ impl Owner {
503
516
  }
504
517
  }
505
518
  pub fn release(&mut self, expected: Applied) -> Result<()> {
506
- if self.retention.is_some() {
519
+ if self.retention.is_some() || self.transition_release.is_some() {
507
520
  return Err(Error::Conflict);
508
521
  }
509
522
  self.validate_receipt(&expected)?;
@@ -561,6 +574,9 @@ impl Owner {
561
574
  }
562
575
  }
563
576
  pub fn close(&mut self) -> Result<()> {
577
+ if let Some(request) = self.transition_release.clone() {
578
+ return self.release_transition(request);
579
+ }
564
580
  if self.retention.is_some() {
565
581
  return Err(Error::Conflict);
566
582
  }
@@ -0,0 +1,107 @@
1
+ use super::*;
2
+
3
+ impl Owner {
4
+ /// Terminal cleanup of an exact caller-retained transition. Unlike reconcile,
5
+ /// this never creates/replaces policy and does not require surviving interfaces.
6
+ pub fn release_transition(&mut self, request: TransitionRelease) -> Result<()> {
7
+ if self.retention.is_some() || request.identity != self.identity {
8
+ return Err(Error::Conflict);
9
+ }
10
+ request.transition.target.validate()?;
11
+ if let Some(previous) = &request.transition.previous {
12
+ self.validate_receipt(previous)?;
13
+ if previous.prepared.policy.kind() != request.transition.target.policy.kind()
14
+ || previous.prepared.policy.revision()
15
+ >= request.transition.target.policy.revision()
16
+ {
17
+ return Err(Error::Conflict);
18
+ }
19
+ }
20
+ if let Some(selected) = &self.transition_release {
21
+ if selected != &request {
22
+ return Err(Error::Conflict);
23
+ }
24
+ if self.released {
25
+ return Ok(());
26
+ }
27
+ } else {
28
+ if self.released
29
+ || self
30
+ .pending
31
+ .as_ref()
32
+ .is_some_and(|pending| pending != &request.transition)
33
+ || self.applied.as_ref().is_some_and(|applied| {
34
+ Some(applied) != request.transition.previous.as_ref()
35
+ && applied.prepared != request.transition.target
36
+ })
37
+ {
38
+ return Err(Error::Conflict);
39
+ }
40
+ // Capture terminal intent before any query, ownership claim or delete.
41
+ self.transition_release = Some(request.clone());
42
+ }
43
+ let result = self.release_transition_inner(&request.transition);
44
+ match result {
45
+ Ok(()) => {
46
+ self.released = true;
47
+ self.pending = None;
48
+ self.error = None;
49
+ self.socket = None;
50
+ Ok(())
51
+ }
52
+ Err(error) => {
53
+ self.error = Some(error);
54
+ Err(error)
55
+ }
56
+ }
57
+ }
58
+
59
+ fn release_transition_inner(&mut self, transition: &Transition) -> Result<()> {
60
+ let Some(graph) = self.graph()? else {
61
+ return Ok(());
62
+ };
63
+ // Also bind any handle this process already observed before an ambiguous
64
+ // delete. A replacement under the same name must never inherit authority.
65
+ if let Some(applied) = &self.applied {
66
+ self.binding(&graph, Some(applied))?;
67
+ }
68
+ let graph = self.adopt(graph, transition)?;
69
+ let prepared = if self.matches(&graph, &transition.target)? {
70
+ transition.target.clone()
71
+ } else {
72
+ transition
73
+ .previous
74
+ .as_ref()
75
+ .ok_or(Error::Conflict)?
76
+ .prepared
77
+ .clone()
78
+ };
79
+ self.applied = Some(self.applied(&graph, prepared)?);
80
+ self.socket()?.batch(
81
+ graph.generation,
82
+ vec![(2, 0, vec![Attr::u64(4, wire::handle(&graph.table, 4)?)])],
83
+ )?;
84
+ if self.graph()?.is_some() {
85
+ return Err(Error::Conflict);
86
+ }
87
+ Ok(())
88
+ }
89
+
90
+ pub(super) fn inspect_transition_release(&self) -> Status {
91
+ // Cleanup intent cannot grant enforcement, including after a failed
92
+ // attempt that acquired OWNER on an otherwise valid persistent graph.
93
+ Status {
94
+ identity: self.identity.clone(),
95
+ state: if self.released {
96
+ "released"
97
+ } else {
98
+ "failed-owned"
99
+ }
100
+ .into(),
101
+ enforced: false,
102
+ applied: self.applied.clone(),
103
+ pending: self.pending.clone(),
104
+ error: self.error.map(|error| error.code().into()),
105
+ }
106
+ }
107
+ }
@@ -27,6 +27,9 @@ mod poolguard_tests;
27
27
  #[path = "owner_detach_tests.rs"]
28
28
  mod detach_tests;
29
29
 
30
+ #[path = "owner_transitionrelease_tests.rs"]
31
+ mod transitionrelease_tests;
32
+
30
33
  #[path = "owner_coexistence_tests.rs"]
31
34
  mod coexistence_tests;
32
35
 
@@ -0,0 +1,327 @@
1
+ use super::namespace_tests::NativeOwner;
2
+ use super::*;
3
+ use serde_json::json;
4
+ use std::io::Write;
5
+ use std::time::{Duration, Instant};
6
+
7
+ fn target(revision: u64) -> Prepared {
8
+ let mut policy = crate::egress::tests::pool_guard();
9
+ policy["revision"] = json!(revision);
10
+ serde_json::from_value::<crate::egress::Policy>(policy)
11
+ .unwrap()
12
+ .prepare()
13
+ .unwrap()
14
+ .into()
15
+ }
16
+ fn request(owner: &Owner, transition: Transition) -> TransitionRelease {
17
+ TransitionRelease {
18
+ identity: owner.identity.clone(),
19
+ transition,
20
+ }
21
+ }
22
+
23
+ #[test]
24
+ fn transition_release_validates_closed_identity_and_body_before_admission() {
25
+ let mut owner = Owner::new(options("release_input")).unwrap();
26
+ let command = request(
27
+ &owner,
28
+ Transition {
29
+ previous: None,
30
+ target: target(1),
31
+ },
32
+ );
33
+ for field in ["bootId", "namespaceDevice", "namespaceInode", "instanceId"] {
34
+ let mut invalid = serde_json::to_value(&command).unwrap();
35
+ invalid["identity"][field] = json!("invalid");
36
+ assert_eq!(
37
+ owner.release_transition(serde_json::from_value(invalid).unwrap()),
38
+ Err(Error::Conflict)
39
+ );
40
+ assert!(owner.transition_release.is_none());
41
+ }
42
+ let mut invalid = command.clone();
43
+ invalid.transition.target.digest = format!("sha256:{}", "0".repeat(64));
44
+ assert!(owner.release_transition(invalid).is_err());
45
+ assert!(owner.transition_release.is_none());
46
+ for pointer in ["", "/identity", "/transition", "/transition/target"] {
47
+ let mut invalid = serde_json::to_value(&command).unwrap();
48
+ invalid.pointer_mut(pointer).unwrap()["extra"] = json!(true);
49
+ assert!(serde_json::from_value::<TransitionRelease>(invalid).is_err());
50
+ }
51
+ let mut invalid = serde_json::to_value(&command).unwrap();
52
+ invalid["transition"]
53
+ .as_object_mut()
54
+ .unwrap()
55
+ .remove("previous");
56
+ assert!(serde_json::from_value::<TransitionRelease>(invalid).is_err());
57
+ assert!(owner.socket.is_none());
58
+ }
59
+
60
+ #[test]
61
+ #[ignore = "requires disposable isolated native qualification guest"]
62
+ fn release_original_transition_after_interfaces_disappear_never_recreates_policy() {
63
+ isolated();
64
+ for replacement in [false, true] {
65
+ for delivered in [false, true] {
66
+ let endpoint = endpoint("a", 72);
67
+ let policy = crate::policy::Policy {
68
+ schema_version: 1,
69
+ revision: 1,
70
+ endpoints: vec![endpoint],
71
+ rules: vec![],
72
+ };
73
+ let mut owner = Owner::new(options("release_links")).unwrap();
74
+ let previous = replacement.then(|| {
75
+ owner
76
+ .reconcile(Transition {
77
+ previous: None,
78
+ target: policy.clone().prepare().unwrap().into(),
79
+ })
80
+ .unwrap()
81
+ });
82
+ let mut next = policy;
83
+ if replacement {
84
+ next.revision = 2;
85
+ }
86
+ let command = request(
87
+ &owner,
88
+ Transition {
89
+ previous,
90
+ target: next.prepare().unwrap().into(),
91
+ },
92
+ );
93
+ if delivered {
94
+ owner.reconcile(command.transition.clone()).unwrap();
95
+ }
96
+ // Only the original command crosses this process boundary.
97
+ drop(owner);
98
+ ip(&["link", "del", "worka"]);
99
+ ip(&["netns", "del", "a"]);
100
+ let mut fresh = Owner::new(options("release_links")).unwrap();
101
+ assert!(fresh.reconcile(command.transition.clone()).is_err());
102
+ if replacement && delivered {
103
+ // Model a retained native pending command after an ambiguous batch.
104
+ fresh.pending = Some(command.transition.clone());
105
+ }
106
+ fresh.release_transition(command.clone()).unwrap();
107
+ let status = fresh.inspect();
108
+ assert_eq!(status.state, "released");
109
+ assert!(!status.enforced);
110
+ assert!(fresh.graph().unwrap().is_none());
111
+ fresh.release_transition(command.clone()).unwrap();
112
+ assert_eq!(
113
+ fresh.reconcile(command.transition.clone()),
114
+ Err(Error::Conflict)
115
+ );
116
+ let mut different = command;
117
+ different.transition.target = target(3);
118
+ assert_eq!(fresh.release_transition(different), Err(Error::Conflict));
119
+ fresh.close().unwrap();
120
+ }
121
+ }
122
+ }
123
+
124
+ #[test]
125
+ #[ignore = "requires disposable isolated native qualification guest"]
126
+ fn release_transition_rejects_foreign_owner_and_keeps_exact_terminal_choice() {
127
+ isolated();
128
+ let mut active = Owner::new(options("release_foreign")).unwrap();
129
+ let command = request(
130
+ &active,
131
+ Transition {
132
+ previous: None,
133
+ target: target(1),
134
+ },
135
+ );
136
+ let applied = active.reconcile(command.transition.clone()).unwrap();
137
+ let mut fresh = Owner::new(options("release_foreign")).unwrap();
138
+ assert_eq!(
139
+ fresh.release_transition(command.clone()),
140
+ Err(Error::ForeignOwner)
141
+ );
142
+ assert_eq!(fresh.inspect().state, "failed-owned");
143
+ assert!(!fresh.inspect().enforced);
144
+ assert_eq!(
145
+ fresh.reconcile(command.transition.clone()),
146
+ Err(Error::Conflict)
147
+ );
148
+ assert_eq!(fresh.detach(applied.clone()), Err(Error::Conflict));
149
+ assert_eq!(fresh.release(applied), Err(Error::Conflict));
150
+ assert!(active.inspect().enforced);
151
+ drop(active);
152
+ // close may retry only the already-selected exact release operation.
153
+ fresh.close().unwrap();
154
+ assert!(fresh.graph().unwrap().is_none());
155
+ fresh.release_transition(command).unwrap();
156
+ }
157
+
158
+ #[test]
159
+ #[ignore = "requires disposable isolated native qualification guest"]
160
+ fn release_transition_binds_previous_handle_and_complete_graph() {
161
+ isolated();
162
+ for fault in ["handle", "body", "pending"] {
163
+ let mut active = Owner::new(options("release_binding")).unwrap();
164
+ let previous = active
165
+ .reconcile(Transition {
166
+ previous: None,
167
+ target: target(1),
168
+ })
169
+ .unwrap();
170
+ let command = request(
171
+ &active,
172
+ Transition {
173
+ previous: Some(previous.clone()),
174
+ target: target(2),
175
+ },
176
+ );
177
+ drop(active);
178
+ let mut fresh = Owner::new(options("release_binding")).unwrap();
179
+ let mut invalid = command.clone();
180
+ if fault == "handle" {
181
+ invalid
182
+ .transition
183
+ .previous
184
+ .as_mut()
185
+ .unwrap()
186
+ .receipt
187
+ .table_handle = "18446744073709551615".into();
188
+ } else if fault == "pending" {
189
+ fresh.pending = Some(Transition {
190
+ previous: Some(previous.clone()),
191
+ target: target(3),
192
+ });
193
+ } else {
194
+ let mut foreign = Socket::open().unwrap();
195
+ let generation = foreign.generation().unwrap();
196
+ foreign
197
+ .batch(
198
+ generation,
199
+ vec![(
200
+ 3,
201
+ 0x600,
202
+ vec![
203
+ Attr::string(1, "snft_release_binding"),
204
+ Attr::string(3, "foreign"),
205
+ ],
206
+ )],
207
+ )
208
+ .unwrap();
209
+ }
210
+ assert_eq!(fresh.release_transition(invalid), Err(Error::Conflict));
211
+ assert!(fresh.graph().unwrap().is_some());
212
+ assert!(!fresh.inspect().enforced);
213
+ drop(fresh);
214
+ // Disposable fixture cleanup never enters the production API.
215
+ let mut cleanup = Socket::open().unwrap();
216
+ let generation = cleanup.generation().unwrap();
217
+ cleanup
218
+ .batch(
219
+ generation,
220
+ vec![(2, 0, vec![Attr::string(1, "snft_release_binding")])],
221
+ )
222
+ .unwrap();
223
+ }
224
+ }
225
+
226
+ #[test]
227
+ #[ignore = "requires disposable isolated native qualification guest"]
228
+ fn release_transition_retry_never_deletes_a_replacement_for_its_observed_handle() {
229
+ isolated();
230
+ let mut owner = Owner::new(options("release_replaced")).unwrap();
231
+ let command = request(
232
+ &owner,
233
+ Transition {
234
+ previous: None,
235
+ target: target(1),
236
+ },
237
+ );
238
+ let first = owner.reconcile(command.transition.clone()).unwrap();
239
+ // Stop at the exact production boundary after deletion, before its final
240
+ // observation. Retained state must still bind the handle already acquired.
241
+ owner.transition_release = Some(command.clone());
242
+ let graph = owner.graph().unwrap().unwrap();
243
+ owner
244
+ .socket()
245
+ .unwrap()
246
+ .batch(
247
+ graph.generation,
248
+ vec![(
249
+ 2,
250
+ 0,
251
+ vec![Attr::u64(4, wire::handle(&graph.table, 4).unwrap())],
252
+ )],
253
+ )
254
+ .unwrap();
255
+ let mut replacement = Owner::new(options("release_replaced")).unwrap();
256
+ let second = replacement.reconcile(command.transition.clone()).unwrap();
257
+ assert_ne!(first.receipt.table_handle, second.receipt.table_handle);
258
+ drop(replacement);
259
+ assert_eq!(owner.release_transition(command), Err(Error::Conflict));
260
+ let graph = owner.graph().unwrap().unwrap();
261
+ assert_eq!(
262
+ wire::handle(&graph.table, 4).unwrap().to_string(),
263
+ second.receipt.table_handle
264
+ );
265
+ assert_eq!(owner.applied, Some(first));
266
+ assert!(!owner.inspect().enforced);
267
+ drop(owner);
268
+ Owner::new(options("release_replaced"))
269
+ .unwrap()
270
+ .release(second)
271
+ .unwrap();
272
+ }
273
+
274
+ #[test]
275
+ #[ignore = "requires disposable isolated native qualification guest"]
276
+ fn release_transition_recovers_lost_delete_ack_across_actual_native_processes() {
277
+ isolated();
278
+ let namespace = std::fs::File::open("/proc/thread-self/ns/net").unwrap();
279
+ let mut native = NativeOwner::start(&namespace);
280
+ let status = native.call(
281
+ "openOwner",
282
+ serde_json::to_value(options("release_ack")).unwrap(),
283
+ );
284
+ let command = TransitionRelease {
285
+ identity: serde_json::from_value(status["identity"].clone()).unwrap(),
286
+ transition: Transition {
287
+ previous: None,
288
+ target: target(1),
289
+ },
290
+ };
291
+ native.call(
292
+ "reconcilePolicy",
293
+ serde_json::to_value(&command.transition).unwrap(),
294
+ );
295
+ writeln!(
296
+ native.child.stdin.as_mut().unwrap(),
297
+ "{}",
298
+ json!({
299
+ "id":"lost-release", "method":"releasePolicyTransition", "params":command,
300
+ })
301
+ )
302
+ .unwrap();
303
+ let mut observer = Owner::new(options("release_ack")).unwrap();
304
+ let until = Instant::now() + Duration::from_secs(3);
305
+ while observer.graph().unwrap().is_some() {
306
+ assert!(Instant::now() < until);
307
+ std::thread::sleep(Duration::from_millis(5));
308
+ }
309
+ // Never read or retain the release response.
310
+ drop(native);
311
+ let mut fresh = NativeOwner::start(&namespace);
312
+ fresh.call(
313
+ "openOwner",
314
+ serde_json::to_value(options("release_ack")).unwrap(),
315
+ );
316
+ assert_eq!(
317
+ fresh.call(
318
+ "releasePolicyTransition",
319
+ serde_json::to_value(&command).unwrap()
320
+ ),
321
+ json!({"released":true})
322
+ );
323
+ let status = fresh.call("inspectPolicy", json!({}));
324
+ assert_eq!(status["state"], "released");
325
+ assert_eq!(status["enforced"], false);
326
+ assert!(observer.graph().unwrap().is_none());
327
+ }
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartnftables',
6
- version: '2.1.0',
6
+ version: '2.2.0',
7
7
  description: 'A TypeScript module for managing nftables rules including NAT, firewall, and rate limiting with a high-level API.'
8
8
  }
@@ -1,6 +1,6 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import type { IManagedNftOptions, IManagedNftPolicy, IManagedNftTransition, IAppliedManagedNftPolicy,
3
- IPreparedManagedNftPolicy, IManagedNftStatus, TManagedNftCommands, TManagedNftPolicy } from './managed.types.js';
3
+ IPreparedManagedNftPolicy, IManagedNftStatus, IManagedNftTransitionRelease, TManagedNftCommands, TManagedNftPolicy } from './managed.types.js';
4
4
 
5
5
  export class ManagedNftablesError extends Error {
6
6
  constructor(public readonly code: string) { super(`Managed nftables ${code}.`); }
@@ -55,6 +55,7 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
55
55
  #retaining = false;
56
56
  #detachExpected: IAppliedManagedNftPolicy<TPolicy> | null = null;
57
57
  #detach: Promise<{ detached: true }> | null = null;
58
+ #transitionRelease: { request: IManagedNftTransitionRelease<TPolicy> } | null = null;
58
59
  readonly #failure = new AbortController();
59
60
  #processLost = false;
60
61
 
@@ -97,7 +98,7 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
97
98
  cliArgs: ['--management', ...(this.#options.networkNamespaceFd === undefined ? [] : ['--network-namespace-fd', '3'])],
98
99
  inheritedFileDescriptors: this.#options.networkNamespaceFd === undefined ? undefined : [this.#options.networkNamespaceFd],
99
100
  readyTimeoutMs: 3000, requestTimeoutMs: 30_000, maxPayloadSize: 262_144,
100
- maxPendingRequestsByMethod: { preparePolicy: 1, openOwner: 1, reconcilePolicy: 1, inspectPolicy: 1, releasePolicy: 1, detachPolicy: 1, closeOwner: 1 },
101
+ maxPendingRequestsByMethod: { preparePolicy: 1, openOwner: 1, reconcilePolicy: 1, inspectPolicy: 1, releasePolicy: 1, releasePolicyTransition: 1, detachPolicy: 1, closeOwner: 1 },
101
102
  });
102
103
  this.#bridge.on('exit', () => {
103
104
  this.#processLost = true;
@@ -123,11 +124,20 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
123
124
  return this.#invoke('releasePolicy', expected);
124
125
  }
125
126
 
127
+ /** Terminal release when an apply acknowledgement was lost. Retain the full
128
+ * original identity and transition first. The native owner accepts only an
129
+ * absent table or the exact previous/target graph; it never recreates policy
130
+ * or requires surviving interfaces. Exact retries and inspection remain
131
+ * available; close() retries admitted cleanup, closeRetaining() only joins. */
132
+ public releaseTransition(request: IManagedNftTransitionRelease<TPolicy>): Promise<{ released: true }> {
133
+ return this.#invoke('releasePolicyTransition', request);
134
+ }
135
+
126
136
  /** Permanently selects retention cleanup, verifies exact PERSIST retention, and
127
137
  * joins the native process. Retain the full applied value before this call.
128
138
  * After any failure close() only terminates; it never deletes the table. */
129
139
  public detach(expected: IAppliedManagedNftPolicy<TPolicy>): Promise<{ detached: true }> {
130
- if (this.#closeRetainingSelected || this.#closeMode === 'delete') return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
140
+ if (this.#closeRetainingSelected || this.#closeMode === 'delete' || this.#transitionRelease) return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
131
141
  this.#retaining = true;
132
142
  let captured: IAppliedManagedNftPolicy<TPolicy>;
133
143
  try {
@@ -173,8 +183,13 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
173
183
  await this.#work?.catch(() => undefined);
174
184
  if (this.#bridge) {
175
185
  if (this.#nativeMayOwn && !retaining) {
176
- const result = await this.#bridge.sendCommand('closeOwner', {});
177
- if (result.released !== true) throw new ManagedNftablesError('PROTOCOL');
186
+ // Even native validation may have rejected before selecting its own
187
+ // terminal intent. Retry this exact request; generic close must not
188
+ // delete an earlier applied graph outside the requested transition.
189
+ const result = this.#transitionRelease
190
+ ? await this.#bridge.sendCommand('releasePolicyTransition', this.#transitionRelease.request)
191
+ : await this.#bridge.sendCommand('closeOwner', {});
192
+ if (result.released !== true || this.#transitionRelease && Object.keys(result).length !== 1) throw new ManagedNftablesError('PROTOCOL');
178
193
  }
179
194
  await this.#bridge.terminate({ gracePeriodMs: 1000 });
180
195
  }
@@ -191,15 +206,24 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
191
206
  if (this.#retaining || !['ready', 'failed-owned'].includes(this.#state) || !this.#bridge || this.#work) return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
192
207
  let captured: TManagedNftCommands<TPolicy>[K]['params'];
193
208
  try { captured = snapshot(params); } catch { return Promise.reject(new ManagedNftablesError('INVALID')); }
194
- if (method === 'reconcilePolicy' || method === 'releasePolicy') this.#nativeMayOwn = true;
209
+ if (this.#transitionRelease && (method !== 'inspectPolicy' && method !== 'releasePolicyTransition'
210
+ || method === 'releasePolicyTransition' && !plugins.util.isDeepStrictEqual(this.#transitionRelease.request, captured))) {
211
+ return Promise.reject(new ManagedNftablesError('CONFLICT'));
212
+ }
213
+ if (method === 'releasePolicyTransition') this.#transitionRelease = { request: captured as IManagedNftTransitionRelease<TPolicy> };
214
+ if (method === 'reconcilePolicy' || method === 'releasePolicy' || method === 'releasePolicyTransition') this.#nativeMayOwn = true;
195
215
  return this.#track(this.#bridge.sendCommand(method, captured).then((result) => {
196
216
  const capturedResult = snapshot(result);
197
- if (method === 'releasePolicy') this.#nativeMayOwn = false;
217
+ if (method === 'releasePolicy' || method === 'releasePolicyTransition') {
218
+ if (method === 'releasePolicyTransition' && ((capturedResult as { released: true })?.released !== true
219
+ || Object.keys(capturedResult).length !== 1)) throw new ManagedNftablesError('PROTOCOL');
220
+ this.#nativeMayOwn = false;
221
+ }
198
222
  if (method === 'inspectPolicy') {
199
223
  const status = capturedResult as IManagedNftStatus<TPolicy>;
200
224
  if (status.state === 'failed-owned' || status.error !== null) this.#fail();
201
225
  }
202
- if (!this.#processLost && this.#state !== 'closing' && (method === 'reconcilePolicy' || method === 'releasePolicy'
226
+ if (!this.#processLost && this.#state !== 'closing' && (method === 'reconcilePolicy' || method === 'releasePolicy' || method === 'releasePolicyTransition'
203
227
  || method === 'inspectPolicy' && (capturedResult as IManagedNftStatus<TPolicy>).error === null
204
228
  && (capturedResult as IManagedNftStatus<TPolicy>).state !== 'failed-owned')) this.#state = 'ready';
205
229
  return capturedResult;
@@ -216,7 +240,7 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
216
240
  && error.code === 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE'
217
241
  && (method === 'preparePolicy' || code === 'INVALID');
218
242
  if (!validation && (this.#state !== 'closing'
219
- || ['reconcilePolicy', 'releasePolicy', 'detachPolicy'].includes(method))) this.#fail();
243
+ || ['reconcilePolicy', 'releasePolicy', 'releasePolicyTransition', 'detachPolicy'].includes(method))) this.#fail();
220
244
  throw new ManagedNftablesError(typeof code === 'string' && /^[A-Z_]{1,64}$/.test(code) ? code : 'UNAVAILABLE');
221
245
  });
222
246
  this.#work = owned;
@@ -60,6 +60,14 @@ export interface IManagedNftTransition<TPolicy extends TManagedNftPolicy = IMana
60
60
  target: IPreparedManagedNftPolicy<TPolicy>;
61
61
  }
62
62
 
63
+ /** Persist the original boot/namespace identity and full transition before cleanup.
64
+ * This releases only its exact owned table; it does not prove packet drain or
65
+ * authorize address reuse. Surviving policy interfaces are not required. */
66
+ export interface IManagedNftTransitionRelease<TPolicy extends TManagedNftPolicy = IManagedNftPolicy> {
67
+ identity: IManagedNftIdentity;
68
+ transition: IManagedNftTransition<TPolicy>;
69
+ }
70
+
63
71
  export interface IManagedNftStatus<TPolicy extends TManagedNftPolicy = IManagedNftPolicy> {
64
72
  identity: IManagedNftIdentity;
65
73
  state: 'idle' | 'applied' | 'failed-owned' | 'released' | 'retained';
@@ -91,6 +99,7 @@ export type TManagedNftCommands<TPolicy extends TManagedNftPolicy = IManagedNftP
91
99
  reconcilePolicy: { params: IManagedNftTransition<TPolicy>; result: IAppliedManagedNftPolicy<TPolicy> };
92
100
  inspectPolicy: { params: Record<string, never>; result: IManagedNftStatus<TPolicy> };
93
101
  releasePolicy: { params: IAppliedManagedNftPolicy<TPolicy>; result: { released: true } };
102
+ releasePolicyTransition: { params: IManagedNftTransitionRelease<TPolicy>; result: { released: true } };
94
103
  detachPolicy: { params: IAppliedManagedNftPolicy<TPolicy>; result: { detached: true } };
95
104
  closeOwner: { params: Record<string, never>; result: { released: true } };
96
105
  };