@push.rocks/smartnftables 2.0.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 +12 -0
- package/dist_rust/smartnftables_linux_amd64_musl +0 -0
- package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +4 -4
- package/dist_rust/smartnftables_linux_arm64_musl +0 -0
- package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +4 -4
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.managednftables.d.ts +13 -1
- package/dist_ts/classes.managednftables.js +67 -14
- package/dist_ts/managed.types.d.ts +13 -0
- package/package.json +1 -1
- package/readme.md +47 -0
- package/rust/src/main.rs +4 -0
- package/rust/src/owner.detach.rs +1 -1
- package/rust/src/owner.rs +18 -2
- package/rust/src/owner.transitionrelease.rs +107 -0
- package/rust/src/owner_tests.rs +3 -0
- package/rust/src/owner_transitionrelease_tests.rs +327 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.managednftables.ts +65 -14
- package/ts/managed.types.ts +9 -0
package/changelog.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
|
|
9
|
+
## 2026-09-15 - 2.1.0
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- Expose a retained failure signal for unexpected policy process loss, ambiguous operations and unconfirmed cleanup while preserving exact recovery and retention semantics.
|
|
14
|
+
|
|
3
15
|
## 2026-09-14 - 2.0.0
|
|
4
16
|
|
|
5
17
|
### Breaking Changes
|
|
Binary file
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "tsrust.build-provenance.v2",
|
|
3
|
-
"binarySha256": "
|
|
3
|
+
"binarySha256": "d997150e9cde38e1ba2c93c5b894c367f9b562a31291ee568ccd1e4a54034b61",
|
|
4
4
|
"buildInfo": {
|
|
5
5
|
"projectName": "@push.rocks/smartnftables",
|
|
6
|
-
"projectVersion": "2.
|
|
7
|
-
"gitCommit": "
|
|
6
|
+
"projectVersion": "2.2.0",
|
|
7
|
+
"gitCommit": "32e9f40066053a15f32031e796e3151140e98eca",
|
|
8
8
|
"gitDirty": false,
|
|
9
|
-
"builtAt": "2026-09-
|
|
9
|
+
"builtAt": "2026-09-15T06:33:44.972Z",
|
|
10
10
|
"tsrustVersion": "1.13.0",
|
|
11
11
|
"binary": "smartnftables",
|
|
12
12
|
"target": "linux_amd64_musl"
|
|
Binary file
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "tsrust.build-provenance.v2",
|
|
3
|
-
"binarySha256": "
|
|
3
|
+
"binarySha256": "14a625bdb893667872dad6b339aa77f64c6d7e538158bff3e4b92e5684d70e61",
|
|
4
4
|
"buildInfo": {
|
|
5
5
|
"projectName": "@push.rocks/smartnftables",
|
|
6
|
-
"projectVersion": "2.
|
|
7
|
-
"gitCommit": "
|
|
6
|
+
"projectVersion": "2.2.0",
|
|
7
|
+
"gitCommit": "32e9f40066053a15f32031e796e3151140e98eca",
|
|
8
8
|
"gitDirty": false,
|
|
9
|
-
"builtAt": "2026-09-
|
|
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.
|
|
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);
|
|
@@ -10,6 +10,10 @@ export declare class ManagedNftables<TPolicy extends TManagedNftPolicy = IManage
|
|
|
10
10
|
#private;
|
|
11
11
|
constructor(options: IManagedNftOptions);
|
|
12
12
|
get state(): string;
|
|
13
|
+
/** Retained loss of owner confidence, including idle process death. It never
|
|
14
|
+
* resets or proves withdrawal/deletion; explicit recovery and joined close
|
|
15
|
+
* remain the caller's responsibility. Orderly close does not abort it. */
|
|
16
|
+
get failureSignal(): AbortSignal;
|
|
13
17
|
/** Starts only the compiler/IPC owner. The returned idle status is not enforcement. */
|
|
14
18
|
start(): Promise<IManagedNftStatus<TPolicy>>;
|
|
15
19
|
prepare(policy: TPolicy): Promise<IPreparedManagedNftPolicy<TPolicy>>;
|
|
@@ -18,6 +22,14 @@ export declare class ManagedNftables<TPolicy extends TManagedNftPolicy = IManage
|
|
|
18
22
|
release(expected: IAppliedManagedNftPolicy<TPolicy>): Promise<{
|
|
19
23
|
released: true;
|
|
20
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
|
+
}>;
|
|
21
33
|
/** Permanently selects retention cleanup, verifies exact PERSIST retention, and
|
|
22
34
|
* joins the native process. Retain the full applied value before this call.
|
|
23
35
|
* After any failure close() only terminates; it never deletes the table. */
|
|
@@ -67,6 +67,9 @@ export class ManagedNftables {
|
|
|
67
67
|
#retaining = false;
|
|
68
68
|
#detachExpected = null;
|
|
69
69
|
#detach = null;
|
|
70
|
+
#transitionRelease = null;
|
|
71
|
+
#failure = new AbortController();
|
|
72
|
+
#processLost = false;
|
|
70
73
|
constructor(options) {
|
|
71
74
|
this.#options = snapshot(options);
|
|
72
75
|
if (Object.keys(this.#options).some((key) => !['ownerId', 'instanceId', 'tableName', 'binaryPath', 'networkNamespaceFd'].includes(key))
|
|
@@ -81,6 +84,16 @@ export class ManagedNftables {
|
|
|
81
84
|
throw new ManagedNftablesError('INVALID');
|
|
82
85
|
}
|
|
83
86
|
get state() { return this.#state; }
|
|
87
|
+
/** Retained loss of owner confidence, including idle process death. It never
|
|
88
|
+
* resets or proves withdrawal/deletion; explicit recovery and joined close
|
|
89
|
+
* remain the caller's responsibility. Orderly close does not abort it. */
|
|
90
|
+
get failureSignal() { return this.#failure.signal; }
|
|
91
|
+
#fail() {
|
|
92
|
+
if (this.#state !== 'closing')
|
|
93
|
+
this.#state = 'failed-owned';
|
|
94
|
+
if (!this.#failure.signal.aborted)
|
|
95
|
+
this.#failure.abort(new ManagedNftablesError('OWNER_LOST'));
|
|
96
|
+
}
|
|
84
97
|
/** Starts only the compiler/IPC owner. The returned idle status is not enforcement. */
|
|
85
98
|
start() {
|
|
86
99
|
if (this.#state !== 'new' || this.#retaining)
|
|
@@ -97,11 +110,12 @@ export class ManagedNftables {
|
|
|
97
110
|
cliArgs: ['--management', ...(this.#options.networkNamespaceFd === undefined ? [] : ['--network-namespace-fd', '3'])],
|
|
98
111
|
inheritedFileDescriptors: this.#options.networkNamespaceFd === undefined ? undefined : [this.#options.networkNamespaceFd],
|
|
99
112
|
readyTimeoutMs: 3000, requestTimeoutMs: 30_000, maxPayloadSize: 262_144,
|
|
100
|
-
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 },
|
|
101
114
|
});
|
|
102
115
|
this.#bridge.on('exit', () => {
|
|
116
|
+
this.#processLost = true;
|
|
103
117
|
if (!['closing', 'stopped'].includes(this.#state))
|
|
104
|
-
this.#
|
|
118
|
+
this.#fail();
|
|
105
119
|
});
|
|
106
120
|
if (!await this.#bridge.spawn())
|
|
107
121
|
throw new ManagedNftablesError('UNAVAILABLE');
|
|
@@ -112,7 +126,7 @@ export class ManagedNftables {
|
|
|
112
126
|
this.#state = 'ready';
|
|
113
127
|
return status;
|
|
114
128
|
})();
|
|
115
|
-
return this.#track(work);
|
|
129
|
+
return this.#track(work, 'openOwner');
|
|
116
130
|
}
|
|
117
131
|
prepare(policy) {
|
|
118
132
|
return this.#invoke('preparePolicy', policy);
|
|
@@ -124,11 +138,19 @@ export class ManagedNftables {
|
|
|
124
138
|
release(expected) {
|
|
125
139
|
return this.#invoke('releasePolicy', expected);
|
|
126
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
|
+
}
|
|
127
149
|
/** Permanently selects retention cleanup, verifies exact PERSIST retention, and
|
|
128
150
|
* joins the native process. Retain the full applied value before this call.
|
|
129
151
|
* After any failure close() only terminates; it never deletes the table. */
|
|
130
152
|
detach(expected) {
|
|
131
|
-
if (this.#closeRetainingSelected || this.#closeMode === 'delete')
|
|
153
|
+
if (this.#closeRetainingSelected || this.#closeMode === 'delete' || this.#transitionRelease)
|
|
132
154
|
return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
133
155
|
this.#retaining = true;
|
|
134
156
|
let captured;
|
|
@@ -151,7 +173,7 @@ export class ManagedNftables {
|
|
|
151
173
|
throw new ManagedNftablesError('PROTOCOL');
|
|
152
174
|
this.#nativeMayOwn = false;
|
|
153
175
|
return result;
|
|
154
|
-
}));
|
|
176
|
+
}), 'detachPolicy');
|
|
155
177
|
const detached = (async () => { const result = await work; await this.close(); return result; })();
|
|
156
178
|
this.#detach = detached;
|
|
157
179
|
void detached.catch(() => { this.#detach = null; });
|
|
@@ -183,8 +205,13 @@ export class ManagedNftables {
|
|
|
183
205
|
await this.#work?.catch(() => undefined);
|
|
184
206
|
if (this.#bridge) {
|
|
185
207
|
if (this.#nativeMayOwn && !retaining) {
|
|
186
|
-
|
|
187
|
-
|
|
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)
|
|
188
215
|
throw new ManagedNftablesError('PROTOCOL');
|
|
189
216
|
}
|
|
190
217
|
await this.#bridge.terminate({ gracePeriodMs: 1000 });
|
|
@@ -194,6 +221,7 @@ export class ManagedNftables {
|
|
|
194
221
|
})().catch(() => {
|
|
195
222
|
this.#state = 'failed-owned';
|
|
196
223
|
this.#close = null;
|
|
224
|
+
this.#fail();
|
|
197
225
|
throw new ManagedNftablesError('CLEANUP_UNCONFIRMED');
|
|
198
226
|
});
|
|
199
227
|
return this.#close;
|
|
@@ -208,23 +236,48 @@ export class ManagedNftables {
|
|
|
208
236
|
catch {
|
|
209
237
|
return Promise.reject(new ManagedNftablesError('INVALID'));
|
|
210
238
|
}
|
|
211
|
-
if (method
|
|
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')
|
|
212
246
|
this.#nativeMayOwn = true;
|
|
213
247
|
return this.#track(this.#bridge.sendCommand(method, captured).then((result) => {
|
|
214
248
|
const capturedResult = snapshot(result);
|
|
215
|
-
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');
|
|
216
253
|
this.#nativeMayOwn = false;
|
|
217
|
-
|
|
218
|
-
|
|
254
|
+
}
|
|
255
|
+
if (method === 'inspectPolicy') {
|
|
256
|
+
const status = capturedResult;
|
|
257
|
+
if (status.state === 'failed-owned' || status.error !== null)
|
|
258
|
+
this.#fail();
|
|
259
|
+
}
|
|
260
|
+
if (!this.#processLost && this.#state !== 'closing' && (method === 'reconcilePolicy' || method === 'releasePolicy' || method === 'releasePolicyTransition'
|
|
261
|
+
|| method === 'inspectPolicy' && capturedResult.error === null
|
|
262
|
+
&& capturedResult.state !== 'failed-owned'))
|
|
219
263
|
this.#state = 'ready';
|
|
220
264
|
return capturedResult;
|
|
221
|
-
}));
|
|
265
|
+
}), method);
|
|
222
266
|
}
|
|
223
|
-
#track(work) {
|
|
267
|
+
#track(work, method) {
|
|
224
268
|
const owned = work.catch((error) => {
|
|
225
269
|
if (this.#state !== 'closing')
|
|
226
270
|
this.#state = 'failed-owned';
|
|
227
271
|
const code = error instanceof plugins.smartrust.RustBridgeRequestError ? error.responseErrorCode : undefined;
|
|
272
|
+
// Only a native compiler rejection or explicit INVALID response proves
|
|
273
|
+
// effect-free validation. Other native errors can follow lost ownership
|
|
274
|
+
// or admitted kernel work. Recovery stays available; confidence is lost.
|
|
275
|
+
const validation = error instanceof plugins.smartrust.RustBridgeRequestError
|
|
276
|
+
&& error.code === 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE'
|
|
277
|
+
&& (method === 'preparePolicy' || code === 'INVALID');
|
|
278
|
+
if (!validation && (this.#state !== 'closing'
|
|
279
|
+
|| ['reconcilePolicy', 'releasePolicy', 'releasePolicyTransition', 'detachPolicy'].includes(method)))
|
|
280
|
+
this.#fail();
|
|
228
281
|
throw new ManagedNftablesError(typeof code === 'string' && /^[A-Z_]{1,64}$/.test(code) ? code : 'UNAVAILABLE');
|
|
229
282
|
});
|
|
230
283
|
this.#work = owned;
|
|
@@ -232,4 +285,4 @@ export class ManagedNftables {
|
|
|
232
285
|
return owned;
|
|
233
286
|
}
|
|
234
287
|
}
|
|
235
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tYW5hZ2VkbmZ0YWJsZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLm1hbmFnZWRuZnRhYmxlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUl4QyxNQUFNLE9BQU8sb0JBQXFCLFNBQVEsS0FBSztJQUM3QyxZQUE0QixJQUFZO1FBQUksS0FBSyxDQUFDLG9CQUFvQixJQUFJLEdBQUcsQ0FBQyxDQUFDO1FBQW5ELFNBQUksR0FBSixJQUFJLENBQVE7SUFBd0MsQ0FBQztDQUNsRjtBQUVELG9GQUFvRjtBQUNwRixNQUFNLFVBQVUsUUFBUSxDQUFJLEtBQVE7SUFDbEMsSUFBSSxLQUFLLEdBQUcsQ0FBQyxDQUFDO0lBQ2QsSUFBSSxXQUFXLEdBQUcsQ0FBQyxDQUFDO0lBQ3BCLE1BQU0sTUFBTSxHQUFHLENBQUMsS0FBYSxFQUFVLEVBQUU7UUFDdkMsSUFBSSxLQUFLLENBQUMsTUFBTSxHQUFHLE9BQU8sSUFBSSxDQUFDLFdBQVcsSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsT0FBTztZQUFFLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUM3SCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUMsQ0FBQztJQUNGLE1BQU0sSUFBSSxHQUFHLENBQUMsS0FBYyxFQUFFLEtBQWEsRUFBVyxFQUFFO1FBQ3RELElBQUksRUFBRSxLQUFLLEdBQUcsTUFBTSxJQUFJLEtBQUssR0FBRyxFQUFFO1lBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzlFLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUTtZQUFFLE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3BELElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxPQUFPLEtBQUssS0FBSyxTQUFTO1lBQUUsT0FBTyxL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
|
|
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.
|
|
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
|
@@ -109,10 +109,12 @@ grants remain outstanding.
|
|
|
109
109
|
| Method | Behavior |
|
|
110
110
|
| --- | --- |
|
|
111
111
|
| `start()` | Starts one private native process and returns idle status. |
|
|
112
|
+
| `failureSignal` | Stable per-instance AbortSignal for lost owner confidence, including an unexpected idle process exit. It never resets or confirms cleanup. |
|
|
112
113
|
| `prepare(policy)` | Validates, canonicalizes and hashes an inert complete policy without kernel writes. |
|
|
113
114
|
| `reconcile({ previous, target })` | Creates or replaces the entire policy in one kernel transaction. `previous` is null initially, otherwise the full prior applied result. |
|
|
114
115
|
| `inspect()` | Reads the current owned graph and reports `enforced`, pending intent and a bounded error code. |
|
|
115
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. |
|
|
116
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. |
|
|
117
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. |
|
|
118
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. |
|
|
@@ -156,6 +158,22 @@ invalidates the old boot receipt; the caller must fence its old link/workload
|
|
|
156
158
|
lifecycle before starting a new owner instance. Cleanup failures and uncertain
|
|
157
159
|
operations must not be reported as successful release or used to transfer authority.
|
|
158
160
|
|
|
161
|
+
Subscribe to `failureSignal` before starting or admitting policy work, and check
|
|
162
|
+
`failureSignal.aborted` when attaching to an existing instance. Unexpected child
|
|
163
|
+
exit, ambiguous operation failures, native inspection reporting failed ownership,
|
|
164
|
+
and unconfirmed cleanup abort the signal once with `ManagedNftablesError` code
|
|
165
|
+
`OWNER_LOST`. Local input rejection, effect-free native compiler validation and
|
|
166
|
+
native `INVALID` responses do not abort it. Other native errors can occur after
|
|
167
|
+
kernel effects or ownership loss and conservatively abort it. Successful orderly
|
|
168
|
+
close, including a cancelled startup, does not signal an unexpected failure;
|
|
169
|
+
ambiguous mutations still signal loss while close is draining them.
|
|
170
|
+
|
|
171
|
+
The caller must fence packet admission on loss. Recovery operations and exact
|
|
172
|
+
retries remain available as documented, but successful recovery never resets the
|
|
173
|
+
signal. It does not monitor foreign kernel changes while idle or prove policy
|
|
174
|
+
withdrawal: PERSIST policy can remain after process death. Still await the chosen
|
|
175
|
+
joined cleanup method and retain unresolved policy/allocation authority.
|
|
176
|
+
|
|
159
177
|
For an orderly restart that must preserve policy, persist the complete applied
|
|
160
178
|
result and call `await owner.detach(applied)` before calling `close()`. The native
|
|
161
179
|
operation validates the exact receipt, graph, interfaces, boot and namespace. It
|
|
@@ -185,6 +203,35 @@ may have left no graph; a lost reply may have left the original or target graph.
|
|
|
185
203
|
Keep the complete original transition durably and recover it through a fresh owner
|
|
186
204
|
with identical options. Never fabricate an applied receipt to request detachment.
|
|
187
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
|
+
|
|
188
235
|
After `closeRetaining()`, all policy commands, including `detach()`, reject.
|
|
189
236
|
Concurrent cleanup calls share the same join. A failed join keeps the native
|
|
190
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}))
|
package/rust/src/owner.detach.rs
CHANGED
|
@@ -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
|
+
}
|
package/rust/src/owner_tests.rs
CHANGED
|
@@ -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
|
+
}
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -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,9 @@ 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;
|
|
59
|
+
readonly #failure = new AbortController();
|
|
60
|
+
#processLost = false;
|
|
58
61
|
|
|
59
62
|
constructor(options: IManagedNftOptions) {
|
|
60
63
|
this.#options = snapshot(options);
|
|
@@ -71,6 +74,16 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
71
74
|
|
|
72
75
|
public get state(): string { return this.#state; }
|
|
73
76
|
|
|
77
|
+
/** Retained loss of owner confidence, including idle process death. It never
|
|
78
|
+
* resets or proves withdrawal/deletion; explicit recovery and joined close
|
|
79
|
+
* remain the caller's responsibility. Orderly close does not abort it. */
|
|
80
|
+
public get failureSignal(): AbortSignal { return this.#failure.signal; }
|
|
81
|
+
|
|
82
|
+
#fail(): void {
|
|
83
|
+
if (this.#state !== 'closing') this.#state = 'failed-owned';
|
|
84
|
+
if (!this.#failure.signal.aborted) this.#failure.abort(new ManagedNftablesError('OWNER_LOST'));
|
|
85
|
+
}
|
|
86
|
+
|
|
74
87
|
/** Starts only the compiler/IPC owner. The returned idle status is not enforcement. */
|
|
75
88
|
public start(): Promise<IManagedNftStatus<TPolicy>> {
|
|
76
89
|
if (this.#state !== 'new' || this.#retaining) return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
@@ -85,10 +98,11 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
85
98
|
cliArgs: ['--management', ...(this.#options.networkNamespaceFd === undefined ? [] : ['--network-namespace-fd', '3'])],
|
|
86
99
|
inheritedFileDescriptors: this.#options.networkNamespaceFd === undefined ? undefined : [this.#options.networkNamespaceFd],
|
|
87
100
|
readyTimeoutMs: 3000, requestTimeoutMs: 30_000, maxPayloadSize: 262_144,
|
|
88
|
-
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 },
|
|
89
102
|
});
|
|
90
103
|
this.#bridge.on('exit', () => {
|
|
91
|
-
|
|
104
|
+
this.#processLost = true;
|
|
105
|
+
if (!['closing', 'stopped'].includes(this.#state)) this.#fail();
|
|
92
106
|
});
|
|
93
107
|
if (!await this.#bridge.spawn()) throw new ManagedNftablesError('UNAVAILABLE');
|
|
94
108
|
const { ownerId, instanceId, tableName } = this.#options;
|
|
@@ -96,7 +110,7 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
96
110
|
if (this.#state !== 'starting') throw new ManagedNftablesError('UNAVAILABLE');
|
|
97
111
|
this.#state = 'ready'; return status;
|
|
98
112
|
})();
|
|
99
|
-
return this.#track(work);
|
|
113
|
+
return this.#track(work, 'openOwner');
|
|
100
114
|
}
|
|
101
115
|
|
|
102
116
|
public prepare(policy: TPolicy): Promise<IPreparedManagedNftPolicy<TPolicy>> {
|
|
@@ -110,11 +124,20 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
110
124
|
return this.#invoke('releasePolicy', expected);
|
|
111
125
|
}
|
|
112
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
|
+
|
|
113
136
|
/** Permanently selects retention cleanup, verifies exact PERSIST retention, and
|
|
114
137
|
* joins the native process. Retain the full applied value before this call.
|
|
115
138
|
* After any failure close() only terminates; it never deletes the table. */
|
|
116
139
|
public detach(expected: IAppliedManagedNftPolicy<TPolicy>): Promise<{ detached: true }> {
|
|
117
|
-
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'));
|
|
118
141
|
this.#retaining = true;
|
|
119
142
|
let captured: IAppliedManagedNftPolicy<TPolicy>;
|
|
120
143
|
try {
|
|
@@ -129,7 +152,7 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
129
152
|
if (result?.detached !== true || Object.keys(result).length !== 1) throw new ManagedNftablesError('PROTOCOL');
|
|
130
153
|
this.#nativeMayOwn = false;
|
|
131
154
|
return result;
|
|
132
|
-
}));
|
|
155
|
+
}), 'detachPolicy');
|
|
133
156
|
const detached = (async () => { const result = await work; await this.close(); return result; })();
|
|
134
157
|
this.#detach = detached;
|
|
135
158
|
void detached.catch(() => { this.#detach = null; });
|
|
@@ -160,14 +183,20 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
160
183
|
await this.#work?.catch(() => undefined);
|
|
161
184
|
if (this.#bridge) {
|
|
162
185
|
if (this.#nativeMayOwn && !retaining) {
|
|
163
|
-
|
|
164
|
-
|
|
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');
|
|
165
193
|
}
|
|
166
194
|
await this.#bridge.terminate({ gracePeriodMs: 1000 });
|
|
167
195
|
}
|
|
168
196
|
this.#bridge = null; this.#state = 'stopped';
|
|
169
197
|
})().catch(() => {
|
|
170
198
|
this.#state = 'failed-owned'; this.#close = null;
|
|
199
|
+
this.#fail();
|
|
171
200
|
throw new ManagedNftablesError('CLEANUP_UNCONFIRMED');
|
|
172
201
|
});
|
|
173
202
|
return this.#close;
|
|
@@ -177,19 +206,41 @@ export class ManagedNftables<TPolicy extends TManagedNftPolicy = IManagedNftPoli
|
|
|
177
206
|
if (this.#retaining || !['ready', 'failed-owned'].includes(this.#state) || !this.#bridge || this.#work) return Promise.reject(new ManagedNftablesError('UNAVAILABLE'));
|
|
178
207
|
let captured: TManagedNftCommands<TPolicy>[K]['params'];
|
|
179
208
|
try { captured = snapshot(params); } catch { return Promise.reject(new ManagedNftablesError('INVALID')); }
|
|
180
|
-
if (method
|
|
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;
|
|
181
215
|
return this.#track(this.#bridge.sendCommand(method, captured).then((result) => {
|
|
182
216
|
const capturedResult = snapshot(result);
|
|
183
|
-
if (method === 'releasePolicy'
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
}
|
|
222
|
+
if (method === 'inspectPolicy') {
|
|
223
|
+
const status = capturedResult as IManagedNftStatus<TPolicy>;
|
|
224
|
+
if (status.state === 'failed-owned' || status.error !== null) this.#fail();
|
|
225
|
+
}
|
|
226
|
+
if (!this.#processLost && this.#state !== 'closing' && (method === 'reconcilePolicy' || method === 'releasePolicy' || method === 'releasePolicyTransition'
|
|
227
|
+
|| method === 'inspectPolicy' && (capturedResult as IManagedNftStatus<TPolicy>).error === null
|
|
228
|
+
&& (capturedResult as IManagedNftStatus<TPolicy>).state !== 'failed-owned')) this.#state = 'ready';
|
|
186
229
|
return capturedResult;
|
|
187
|
-
}));
|
|
230
|
+
}), method);
|
|
188
231
|
}
|
|
189
|
-
#track<T>(work: Promise<T>): Promise<T> {
|
|
232
|
+
#track<T>(work: Promise<T>, method: keyof TManagedNftCommands<TPolicy>): Promise<T> {
|
|
190
233
|
const owned = work.catch((error) => {
|
|
191
234
|
if (this.#state !== 'closing') this.#state = 'failed-owned';
|
|
192
235
|
const code = error instanceof plugins.smartrust.RustBridgeRequestError ? error.responseErrorCode : undefined;
|
|
236
|
+
// Only a native compiler rejection or explicit INVALID response proves
|
|
237
|
+
// effect-free validation. Other native errors can follow lost ownership
|
|
238
|
+
// or admitted kernel work. Recovery stays available; confidence is lost.
|
|
239
|
+
const validation = error instanceof plugins.smartrust.RustBridgeRequestError
|
|
240
|
+
&& error.code === 'ERR_RUST_BRIDGE_REQUEST_RUST_RESPONSE'
|
|
241
|
+
&& (method === 'preparePolicy' || code === 'INVALID');
|
|
242
|
+
if (!validation && (this.#state !== 'closing'
|
|
243
|
+
|| ['reconcilePolicy', 'releasePolicy', 'releasePolicyTransition', 'detachPolicy'].includes(method))) this.#fail();
|
|
193
244
|
throw new ManagedNftablesError(typeof code === 'string' && /^[A-Z_]{1,64}$/.test(code) ? code : 'UNAVAILABLE');
|
|
194
245
|
});
|
|
195
246
|
this.#work = owned;
|
package/ts/managed.types.ts
CHANGED
|
@@ -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
|
};
|