@homeflare/alchemy 0.57.0 → 0.58.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/dist/unifi/firewall-policy-drift.d.ts +3 -3
- package/dist/unifi/firewall-policy-drift.d.ts.map +1 -1
- package/dist/unifi/firewall-policy-form.d.ts +18 -15
- package/dist/unifi/firewall-policy-form.d.ts.map +1 -1
- package/dist/unifi/index.js +64 -8
- package/dist/unifi/index.js.map +5 -5
- package/dist/unifi/network-update.fixtures.d.ts +53 -0
- package/dist/unifi/network-update.fixtures.d.ts.map +1 -0
- package/dist/unifi/update-reconcile.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/unifi/firewall-policy-drift.test.ts +64 -0
- package/src/unifi/firewall-policy-drift.ts +24 -7
- package/src/unifi/firewall-policy-form.ts +61 -15
- package/src/unifi/network-update-accept.test.ts +59 -0
- package/src/unifi/network-update.fixtures.ts +74 -0
- package/src/unifi/network-update.test.ts +11 -69
- package/src/unifi/update-reconcile.ts +37 -3
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* before comparing — never applied to only `live` or only `declared` (the specific gap MEDIUM-4's
|
|
11
11
|
* own mutant found in `network-drift.ts`; see its header, and `acl-rule-drift.ts`'s own repeat of
|
|
12
12
|
* this same rule for `protocolFilter`/`enforcingDeviceFilter`).
|
|
13
|
-
* ★ `
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* ★ `destination`/`ipProtocolScope`/`schedule`/`source` run through `normalizeSetArrays` on BOTH
|
|
14
|
+
* sides (set-like nested arrays compare as sets; `firewall-policy-form.ts`'s header), then the
|
|
15
|
+
* default `deepEqual`. `action` compares wholesale.
|
|
16
16
|
*/
|
|
17
17
|
import type * as firewall from '@distilled.cloud/unifi-network/firewall';
|
|
18
18
|
import { type FirewallPolicyAttributes, type FirewallPolicyProps } from './firewall-policy-form.ts';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"firewall-policy-drift.d.ts","sourceRoot":"","sources":["../../src/unifi/firewall-policy-drift.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,KAAK,QAAQ,MAAM,yCAAyC,CAAC;AAEzE,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,
|
|
1
|
+
{"version":3,"file":"firewall-policy-drift.d.ts","sourceRoot":"","sources":["../../src/unifi/firewall-policy-drift.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,KAAK,QAAQ,MAAM,yCAAyC,CAAC;AAEzE,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EAIzB,MAAM,2BAA2B,CAAC;AAoCnC,eAAO,MAAM,OAAO,eACN,wBAAwB,SAC7B,mBAAmB,KACzB,OAAqD,CAAC;AAEzD;;;;GAIG;AACH,eAAO,MAAM,OAAO,SAAU,QAAQ,CAAC,cAAc,SAAS,mBAAmB,sCACnC,CAAC"}
|
|
@@ -15,17 +15,18 @@
|
|
|
15
15
|
* `deepEqual` (`stripNullish`) as ONE value, same proportion `acl-rule-form.ts`'s
|
|
16
16
|
* `enforcingDeviceFilter` gets (one level of structure, not exploded into scalars).
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `FirewallPolicyMACAddressFilter.macAddresses`, `FirewallPolicyNetworkFilter.networkIds`,
|
|
22
|
-
* `FirewallPolicyApplicationFilter.applicationIds`, `
|
|
23
|
-
* `
|
|
24
|
-
* `
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
18
|
+
* ★ SET-LIKE NESTED ARRAYS COMPARE AS SETS (closed 2026-10-19; was T15's accepted gap). The
|
|
19
|
+
* vendor's filter variants inside `source`/`destination`/`ipProtocolScope`/`schedule` carry
|
|
20
|
+
* arrays with no documented order: `FirewallPolicyIPAddressFilter.items`, the port filter's
|
|
21
|
+
* `items`, `FirewallPolicyMACAddressFilter.macAddresses`, `FirewallPolicyNetworkFilter.networkIds`,
|
|
22
|
+
* `FirewallPolicyApplicationFilter.applicationIds`, `FirewallPolicyRegionFilter.regions`,
|
|
23
|
+
* `FirewallPolicyVPNServerFilter.vpnServerIds`, `FirewallPolicyDomainFilter.domains`,
|
|
24
|
+
* `applicationCategoryIds` and `FirewallSchedule.repeatOnDays`. Measured:
|
|
25
|
+
* the controller (Network 11.0.83) stored a declared port list [443, 9100] as [9100, 443], so a
|
|
26
|
+
* correct create failed its read-back (`UnifiCreateDidNotConverge`, homeflare-network PR 79).
|
|
27
|
+
* `normalizeSetArrays` sorts every array under one of those keys (by canonical JSON) on BOTH
|
|
28
|
+
* sides before `deepEqual`. Membership still has to match exactly, and duplicates are kept, so a
|
|
29
|
+
* real change is never hidden.
|
|
29
30
|
*
|
|
30
31
|
* ⛔ `connectionStateFilter` IS THE ONE TOP-LEVEL PROPS ARRAY, AND IS A SET — "Match on firewall
|
|
31
32
|
* connection state. If null, matches all connection states" describes membership, not a sequence
|
|
@@ -48,6 +49,8 @@ import type * as firewall from '@distilled.cloud/unifi-network/firewall';
|
|
|
48
49
|
* see `acl-rule-form.ts`'s own header for why a normalizer applied to only one side is a bug
|
|
49
50
|
* class (MEDIUM-4). */
|
|
50
51
|
export declare const normalizeConnectionStateFilter: (value: readonly string[] | undefined) => string[] | undefined;
|
|
52
|
+
/** A deep copy with every SET_ARRAY_KEYS array sorted by canonical JSON (duplicates kept). */
|
|
53
|
+
export declare const normalizeSetArrays: <T>(value: T) => T;
|
|
51
54
|
/** ⚠️ EVERY OPTIONAL FIELD SPELLS OUT `| undefined` — `exactOptionalPropertyTypes`; see
|
|
52
55
|
* `network-form.ts`'s own header.
|
|
53
56
|
*
|
|
@@ -64,17 +67,17 @@ export interface FirewallPolicyProps {
|
|
|
64
67
|
/** Connection states this policy matches — compared as a SET, see the header. */
|
|
65
68
|
connectionStateFilter?: string[] | undefined;
|
|
66
69
|
description?: string | undefined;
|
|
67
|
-
/** Traffic destination — compound, compared wholesale; see the header
|
|
70
|
+
/** Traffic destination — compound, compared wholesale with set-like arrays normalized; see the header. */
|
|
68
71
|
destination: firewall.FirewallPolicyDestination;
|
|
69
72
|
enabled: boolean;
|
|
70
|
-
/** IP version + protocol scope — compound, compared wholesale; see the header
|
|
73
|
+
/** IP version + protocol scope — compound, compared wholesale with set-like arrays normalized; see the header. */
|
|
71
74
|
ipProtocolScope: firewall.FirewallPolicyIPProtocolScope;
|
|
72
75
|
ipsecFilter?: string | undefined;
|
|
73
76
|
loggingEnabled: boolean;
|
|
74
77
|
name: string;
|
|
75
|
-
/** Schedule this policy is active on — compound, compared wholesale; see the header
|
|
78
|
+
/** Schedule this policy is active on — compound, compared wholesale with set-like arrays normalized; see the header. */
|
|
76
79
|
schedule?: firewall.FirewallSchedule | undefined;
|
|
77
|
-
/** Traffic source — compound, compared wholesale; see the header
|
|
80
|
+
/** Traffic source — compound, compared wholesale with set-like arrays normalized; see the header. */
|
|
78
81
|
source: firewall.FirewallPolicySource;
|
|
79
82
|
}
|
|
80
83
|
export interface FirewallPolicyAttributes {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"firewall-policy-form.d.ts","sourceRoot":"","sources":["../../src/unifi/firewall-policy-form.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"firewall-policy-form.d.ts","sourceRoot":"","sources":["../../src/unifi/firewall-policy-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,OAAO,KAAK,KAAK,QAAQ,MAAM,yCAAyC,CAAC;AAIzE;;wBAEwB;AACxB,eAAO,MAAM,8BAA8B,UAClC,SAAS,MAAM,EAAE,GAAG,SAAS,KACnC,MAAM,EAAE,GAAG,SAA2D,CAAC;AA6B1E,8FAA8F;AAC9F,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,CAAC,KAAG,CAehD,CAAC;AAEF;;;;;;;;6FAQ6F;AAC7F,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,MAAM,EAAE,QAAQ,CAAC,oBAAoB,CAAC;IACtC,iFAAiF;IACjF,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,0GAA0G;IAC1G,WAAW,EAAE,QAAQ,CAAC,yBAAyB,CAAC;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,kHAAkH;IAClH,eAAe,EAAE,QAAQ,CAAC,6BAA6B,CAAC;IACxD,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,cAAc,EAAE,OAAO,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,wHAAwH;IACxH,QAAQ,CAAC,EAAE,QAAQ,CAAC,gBAAgB,GAAG,SAAS,CAAC;IACjD,qGAAqG;IACrG,MAAM,EAAE,QAAQ,CAAC,oBAAoB,CAAC;CACvC;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,QAAQ,CAAC,oBAAoB,CAAC;IACtC,qBAAqB,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5C,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,QAAQ,CAAC,yBAAyB,CAAC;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,oGAAoG;IACpG,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,QAAQ,CAAC,6BAA6B,CAAC;IACxD,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,cAAc,EAAE,OAAO,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAChD,MAAM,EAAE,QAAQ,CAAC,oBAAoB,CAAC;IACtC,6FAA6F;IAC7F,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,YAAY,SACjB,QAAQ,CAAC,cAAc,SACtB,mBAAmB,KACzB,wBAgBD,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,SAC1B,QAAQ,CAAC,cAAc,UACrB,MAAM,KACb,mBAcD,CAAC"}
|
package/dist/unifi/index.js
CHANGED
|
@@ -318,6 +318,15 @@ var computePatch = (news, olds, patchKeys) => {
|
|
|
318
318
|
}
|
|
319
319
|
return { set, unset };
|
|
320
320
|
};
|
|
321
|
+
var acceptInto = (olds, news, accepted) => {
|
|
322
|
+
const next = { ...olds };
|
|
323
|
+
for (const field of accepted) {
|
|
324
|
+
const key = String(field);
|
|
325
|
+
if (key in news || key in next)
|
|
326
|
+
next[key] = news[key];
|
|
327
|
+
}
|
|
328
|
+
return next;
|
|
329
|
+
};
|
|
321
330
|
var updateReconcile = (spec, { news, olds, output, note }) => Effect5.gen(function* () {
|
|
322
331
|
const identity = spec.describe(news);
|
|
323
332
|
const live = yield* spec.fetchLive(news);
|
|
@@ -334,15 +343,22 @@ var updateReconcile = (spec, { news, olds, output, note }) => Effect5.gen(functi
|
|
|
334
343
|
if (previous !== identity) {
|
|
335
344
|
return yield* new UnifiIdentityChanged({ type: spec.type, identity, previous });
|
|
336
345
|
}
|
|
346
|
+
const disagrees = new Set(update.driftOf(live, news).map((d) => String(d.field)));
|
|
337
347
|
const drifted = update.driftOf(live, olds).map((d) => d.field);
|
|
338
|
-
|
|
348
|
+
const conflicts = drifted.filter((field) => disagrees.has(String(field)));
|
|
349
|
+
if (conflicts.length > 0) {
|
|
339
350
|
return yield* new UnifiLiveDriftedSinceDeploy({
|
|
340
351
|
type: spec.type,
|
|
341
352
|
identity,
|
|
342
|
-
fields:
|
|
353
|
+
fields: conflicts
|
|
343
354
|
});
|
|
344
355
|
}
|
|
345
|
-
const
|
|
356
|
+
const accepted = drifted.filter((field) => !disagrees.has(String(field)));
|
|
357
|
+
const baseline = acceptInto(olds, news, accepted);
|
|
358
|
+
if (accepted.length > 0) {
|
|
359
|
+
yield* note(`${spec.type} ${identity}: live edits since the last deploy already match the declaration, accepted: [${accepted.map(String).join(", ")}]`);
|
|
360
|
+
}
|
|
361
|
+
const patch = computePatch(news, baseline, update.patchKeys);
|
|
346
362
|
if (Object.keys(patch.set).length === 0 && patch.unset.length === 0) {
|
|
347
363
|
return yield* refuseWrite(spec.type, identity, "update");
|
|
348
364
|
}
|
|
@@ -936,6 +952,30 @@ var writeFirewallPolicyCreate = (props) => firewall2.createFirewallPolicy(firewa
|
|
|
936
952
|
// src/unifi/firewall-policy-form.ts
|
|
937
953
|
var sortedSet4 = (values) => [...new Set(values)].sort();
|
|
938
954
|
var normalizeConnectionStateFilter = (value) => value == null ? undefined : sortedSet4(value);
|
|
955
|
+
var SET_ARRAY_KEYS = new Set([
|
|
956
|
+
"items",
|
|
957
|
+
"macAddresses",
|
|
958
|
+
"networkIds",
|
|
959
|
+
"applicationIds",
|
|
960
|
+
"regions",
|
|
961
|
+
"vpnServerIds",
|
|
962
|
+
"repeatOnDays",
|
|
963
|
+
"domains",
|
|
964
|
+
"applicationCategoryIds"
|
|
965
|
+
]);
|
|
966
|
+
var sortKeys = (value) => Array.isArray(value) ? value.map(sortKeys) : value !== null && typeof value === "object" ? Object.fromEntries(Object.keys(value).sort().map((k) => [k, sortKeys(value[k])])) : value;
|
|
967
|
+
var canonical = (value) => JSON.stringify(sortKeys(normalizeSetArrays(value)));
|
|
968
|
+
var normalizeSetArrays = (value) => {
|
|
969
|
+
if (Array.isArray(value))
|
|
970
|
+
return value.map((v) => normalizeSetArrays(v));
|
|
971
|
+
if (value === null || typeof value !== "object")
|
|
972
|
+
return value;
|
|
973
|
+
const out = {};
|
|
974
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
975
|
+
out[key] = SET_ARRAY_KEYS.has(key) && Array.isArray(inner) ? [...inner].map((v) => normalizeSetArrays(v)).sort((x, y) => canonical(x) < canonical(y) ? -1 : canonical(x) > canonical(y) ? 1 : 0) : normalizeSetArrays(inner);
|
|
976
|
+
}
|
|
977
|
+
return out;
|
|
978
|
+
};
|
|
939
979
|
var attributesOf7 = (live, props) => ({
|
|
940
980
|
siteId: props.siteId,
|
|
941
981
|
firewallPolicyId: live.id,
|
|
@@ -978,14 +1018,30 @@ var fieldDrift7 = makeDriftOf([
|
|
|
978
1018
|
declared: (p) => normalizeConnectionStateFilter(p.connectionStateFilter)
|
|
979
1019
|
},
|
|
980
1020
|
{ field: "description", live: (a) => a.description, declared: (p) => p.description },
|
|
981
|
-
{
|
|
1021
|
+
{
|
|
1022
|
+
field: "destination",
|
|
1023
|
+
live: (a) => normalizeSetArrays(a.destination),
|
|
1024
|
+
declared: (p) => normalizeSetArrays(p.destination)
|
|
1025
|
+
},
|
|
982
1026
|
{ field: "enabled", live: (a) => a.enabled, declared: (p) => p.enabled },
|
|
983
|
-
{
|
|
1027
|
+
{
|
|
1028
|
+
field: "ipProtocolScope",
|
|
1029
|
+
live: (a) => normalizeSetArrays(a.ipProtocolScope),
|
|
1030
|
+
declared: (p) => normalizeSetArrays(p.ipProtocolScope)
|
|
1031
|
+
},
|
|
984
1032
|
{ field: "ipsecFilter", live: (a) => a.ipsecFilter, declared: (p) => p.ipsecFilter },
|
|
985
1033
|
{ field: "loggingEnabled", live: (a) => a.loggingEnabled, declared: (p) => p.loggingEnabled },
|
|
986
1034
|
{ field: "name", live: (a) => a.name, declared: (p) => p.name },
|
|
987
|
-
{
|
|
988
|
-
|
|
1035
|
+
{
|
|
1036
|
+
field: "schedule",
|
|
1037
|
+
live: (a) => normalizeSetArrays(a.schedule),
|
|
1038
|
+
declared: (p) => normalizeSetArrays(p.schedule)
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
field: "source",
|
|
1042
|
+
live: (a) => normalizeSetArrays(a.source),
|
|
1043
|
+
declared: (p) => normalizeSetArrays(p.source)
|
|
1044
|
+
}
|
|
989
1045
|
]);
|
|
990
1046
|
var matches7 = (attributes, props) => fieldDrift7(attributes, props).length === 0;
|
|
991
1047
|
var driftOf7 = (live, props) => fieldDrift7(attributesOf7(live, props), props);
|
|
@@ -1195,5 +1251,5 @@ export {
|
|
|
1195
1251
|
driftOf6 as wifiBroadcastDriftOf
|
|
1196
1252
|
};
|
|
1197
1253
|
|
|
1198
|
-
//# debugId=
|
|
1254
|
+
//# debugId=D14D4404F58A5F1264756E2164756E21
|
|
1199
1255
|
//# sourceMappingURL=index.js.map
|