diff --git a/crates/spt-daemon/src/bootstrap_firewall/windows.rs b/crates/spt-daemon/src/bootstrap_firewall/windows.rs index 56ff9176..109c9c61 100644 --- a/crates/spt-daemon/src/bootstrap_firewall/windows.rs +++ b/crates/spt-daemon/src/bootstrap_firewall/windows.rs @@ -29,7 +29,7 @@ const RULE_GROUP: &str = "spt-core bootstrap TCP"; /// and `desired_specs` all derive from these constants instead of repeating them. const RULE_NAMES: &[&str] = &[RULE_NAME_TAILNET, RULE_NAME_LAN]; -/// The one ActiveStore enforcement code that certifies a rule is in force. +/// The positive ActiveStore code, combined with PrimaryStatus and the code-20 veto. /// /// A NUMBER, NEVER A SPELLING. The host renders this code as a display string, and /// which string is a property of the host rather than of the rule: measured on @@ -41,19 +41,12 @@ const RULE_NAMES: &[&str] = &[RULE_NAME_TAILNET, RULE_NAME_LAN]; // [impl->REQ-BOOTSTRAP-FIREWALL-ENFORCEMENT-CODES] const ENFORCEMENT_SUCCESS: u16 = 1; -/// The COMPLETE certified evidence: exactly one element, the success code. -/// -/// STRICTNESS IS THE REQUIREMENT, not an implementation detail. This is compared with -/// `!=` against the whole slice, so `[1, 1]` and `[1, 5]` both refuse. Equality must -/// never become membership: a success code sitting beside another code means the host -/// reported something else as well. What an array of several codes MEANS is not -/// resolved -- the documented values describe individual codes and say nothing about -/// how a multi-element array should be read -- so this policy refuses rather than -/// interpreting. Empty, unknown and additional values all refuse here, and -/// anything the query could not transport as a number never reaches this comparison at -/// all -- it fails deserialization and surfaces as a query error instead. +/// Run-02 on HFENDULEAM measured OK/[5,1] without a package binding and +/// Inactive/[5,20] with `-Package Any`. A multi-profile rule need not have a +/// singleton enforcement array: the inactive Domain profile contributes 5. // [impl->REQ-BOOTSTRAP-FIREWALL-ENFORCEMENT-CODES] -const ENFORCEMENT_CERTIFIED: [u16; 1] = [ENFORCEMENT_SUCCESS]; +const ENFORCEMENT_NO_LOCAL_USER: u16 = 20; +const PRIMARY_STATUS_OK: u16 = 1; // Enumerate then compare the exact immutable name: a failed query must not be // confused with NetSecurity's non-terminating 'named object not found' error. @@ -74,11 +67,11 @@ function Value($object, $name) { # verdict compared against "Full" -- so the success value itself was refused and no host # state could satisfy the arm. The repair is this ACCESSOR, not a cast: casting the # adapted value throws on "Enforced". CimInstanceProperties is the unadapted CIM view. -function RawValue($object, $name) { +function RawValue($object, $name, $expectedType = 'UInt16Array') { $property = $object.CimInstanceProperties[$name] if ($null -eq $property) { throw "ENFORCEMENT_REPRESENTATION_FAULT: NetSecurity omitted required CIM property $name" } - if ([string]$property.CimType -ne 'UInt16Array') { - throw "ENFORCEMENT_REPRESENTATION_FAULT: CIM property $name has type $($property.CimType), expected UInt16Array" + if ([string]$property.CimType -ne $expectedType) { + throw "ENFORCEMENT_REPRESENTATION_FAULT: CIM property $name has type $($property.CimType), expected $expectedType" } $property.Value } @@ -142,12 +135,14 @@ function Describe($rule) { # NOT: it is reported below and decided in Rust, because a policy expressed # only as text handed to another interpreter cannot be unit-tested, which is # how two contradictory amendments coexisted with nothing red (releases#304). + # Run-02 measured an empty Package when New-NetFirewallRule omitted -Package. + # Literal Any is a binding here, unlike the Program filter's unrestricted spelling. $hygiene = (Is $rule 'Enabled' 'True') -and (Is $rule 'Direction' 'Inbound') -and (Is $rule 'Action' 'Allow') -and (Empty $rule 'Platform') -and (Empty $rule 'Owner') -and (Is $port 'RemotePort' 'Any') -and (Is $port 'DynamicTarget' 'Any') -and (Is $address 'LocalAddress' 'Any') -and - (Is $app 'Package' 'Any') -and (Is $service 'Service' 'Any') -and + (Empty $app 'Package') -and (Is $service 'Service' 'Any') -and (Is $interface 'InterfaceAlias' 'Any') -and (Is $type 'InterfaceType' 'Any') -and (Is $security 'Authentication' 'NotRequired') -and (Is $security 'Encryption' 'NotRequired') -and (Is $security 'OverrideBlockRules' 'False') -and @@ -185,6 +180,10 @@ function Describe($rule) { } [int]$_ }) + $primaryStatusRaw = RawValue $rule 'PrimaryStatus' 'UInt16' + if ($null -eq $primaryStatusRaw -or $primaryStatusRaw -isnot [uint16]) { + throw "ENFORCEMENT_REPRESENTATION_FAULT: invalid raw PrimaryStatus for $name, expected System.UInt16" + } # WHERE THE EFFECTIVE RULE CAME FROM. Reported, never judged here: the Rust # side decides what counts as persistent. 'Local' names the local persistent # store as this effective rule's source, which is how ONE ActiveStore pass @@ -199,6 +198,7 @@ function Describe($rule) { remotes = @(Value $address 'RemoteAddress' | ForEach-Object { [string]$_ }) hygiene = [bool]$hygiene enforcement = $enforcement + primaryStatus = [int]$primaryStatusRaw } } # ONE STORE PASS, NOT TWO. The old query enumerated PersistentStore and @@ -259,14 +259,12 @@ struct Rule { /// anything the query cannot transport as a number -- a null, a display /// string, a negative -- fails deserialization and surfaces as a query error /// rather than arriving as a code the verdict would then judge. - /// - /// NO NAME IS ATTACHED TO ANY CODE, here or anywhere downstream. The captured - /// class on the measuring host exposes ValueMap (0..25) with the Values - /// qualifier ABSENT, so no code-to-name mapping is derivable from the host at - /// all -- including for the success code. Naming one would be documentation - /// smuggled in as an observation. + /// PrimaryStatus is transported separately so a success code cannot override + /// the provider reporting the rule inactive. // [impl->REQ-BOOTSTRAP-FIREWALL-ENFORCEMENT-CODES] enforcement: Vec, + #[serde(rename = "primaryStatus")] + primary_status: u16, /// Which store this EFFECTIVE rule came from, as NetSecurity reports it: /// `Local` for the local persistent store, `GroupPolicy` for domain policy, /// `Dynamic` for a rule that exists only until reboot. Reported here and @@ -405,7 +403,11 @@ fn lan_scope(addresses: &[Address]) -> LanScope { .collect(); prefixes.sort(); prefixes.dedup(); - if prefixes.is_empty() { LanScope::NoneConnected } else { LanScope::Prefixes(prefixes) } + if prefixes.is_empty() { + LanScope::NoneConnected + } else { + LanScope::Prefixes(prefixes) + } } /// The face of a host that cannot carry the LAN half at all. @@ -431,7 +433,10 @@ fn desired_specs(binder_normalized: &str, bound_port: u16, lan: &LanScope) -> Ve name: RULE_NAME_TAILNET, port: bound_port, profile: DESIRED_TAILNET_PROFILE.to_string(), - remotes: DESIRED_TAILNET_REMOTES.iter().map(|s| s.to_string()).collect(), + remotes: DESIRED_TAILNET_REMOTES + .iter() + .map(|s| s.to_string()) + .collect(), program: program.clone(), }]; // THE LAN HALF IS OMITTED, NOT EMPTIED, when the host carries no LAN. A spec @@ -456,6 +461,8 @@ fn desired_specs(binder_normalized: &str, bound_port: u16, lan: &LanScope) -> Ve /// the emitted rule text can be compared against the spec WITHOUT a live /// NetSecurity, and an effector whose output nothing can read is how a scope /// policy expressed as script text went unchecked in the first place. +/// Run-02 isolated `-Package Any` as the argument making the rule inactive. +/// Omit the binding; Get-NetFirewallApplicationFilter reports an empty Package. // [impl->REQ-WEB-LAN-BOOTSTRAP-FIREWALL] fn render_writes(want: &[RuleSpec]) -> String { want.iter() @@ -465,7 +472,7 @@ fn render_writes(want: &[RuleSpec]) -> String { r#" New-NetFirewallRule -PolicyStore PersistentStore -Name '{name}' -DisplayName '{name}' -Group $ruleGroup ` -Enabled True -Direction Inbound -Action Allow -Profile {profile} -Protocol TCP -LocalPort {port} ` - -RemotePort Any -LocalAddress Any -RemoteAddress {remotes} {program_arg}-Package Any -Service Any ` + -RemotePort Any -LocalAddress Any -RemoteAddress {remotes} {program_arg}-Service Any ` -InterfaceAlias Any -InterfaceType Any -Authentication NotRequired -Encryption NotRequired ` -LocalUser Any -RemoteUser Any -RemoteMachine Any -OverrideBlockRules $false -DynamicTarget Any ` -EdgeTraversalPolicy Block -ErrorAction Stop | Out-Null @@ -600,7 +607,11 @@ fn ipv4_network(text: &str) -> Option { } }, }; - let mask = if prefix == 0 { 0 } else { u32::MAX << (32 - prefix) }; + let mask = if prefix == 0 { + 0 + } else { + u32::MAX << (32 - prefix) + }; let network = std::net::Ipv4Addr::from(u32::from(address) & mask); Some(format!("{network}/{prefix}")) } @@ -638,8 +649,10 @@ fn remote_set(remotes: &[String]) -> Vec { /// what the next constant or the next composer would quietly break. // [impl->REQ-BOOTSTRAP-FIREWALL-SPELLING-EQUIVALENCE] fn wanted_networks(remotes: &[String]) -> Option> { - let mut networks: Vec = - remotes.iter().map(|remote| ipv4_network(remote)).collect::>()?; + let mut networks: Vec = remotes + .iter() + .map(|remote| ipv4_network(remote)) + .collect::>()?; networks.sort(); Some(networks) } @@ -749,7 +762,13 @@ fn powershell(leg: &str, script: &str) -> Result { super::run( leg, "powershell.exe", - &["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", &command], + &[ + "-NoLogo", + "-NoProfile", + "-NonInteractive", + "-EncodedCommand", + &command, + ], NETSECURITY_BUDGET, ) } @@ -801,8 +820,17 @@ fn decide(state: &Snapshot, expected_program: &str, port: u16) -> Result 1` guard would refuse // its own happy path the moment the query began enumerating the name set. for spec in &want { - if state.active.iter().filter(|rule| rule.name == spec.name).count() > 1 { - return Err(format!("Ambiguous duplicate bootstrap rules named {}", spec.name)); + if state + .active + .iter() + .filter(|rule| rule.name == spec.name) + .count() + > 1 + { + return Err(format!( + "Ambiguous duplicate bootstrap rules named {}", + spec.name + )); } } if !pair_satisfied_by(&state.active, &want) { @@ -832,11 +860,15 @@ fn decide(state: &Snapshot, expected_program: &str, port: u16) -> ResultREQ-BOOTSTRAP-FIREWALL-ENFORCEMENT-CODES] - if rule.enforcement.as_slice() != ENFORCEMENT_CERTIFIED { + if rule.primary_status != PRIMARY_STATUS_OK + || !rule.enforcement.contains(&ENFORCEMENT_SUCCESS) + || rule.enforcement.contains(&ENFORCEMENT_NO_LOCAL_USER) + { return Err(format!( - "Bootstrap rule {} is configured but ActiveStore enforcement codes are {:?}, \ - not exactly [{}]", - rule.name, rule.enforcement, ENFORCEMENT_SUCCESS + "Bootstrap rule {} is configured but ActiveStore PrimaryStatus is {} and \ + enforcement codes are {:?}; expected PrimaryStatus {} with code {} present and code {} absent", + rule.name, rule.primary_status, rule.enforcement, PRIMARY_STATUS_OK, + ENFORCEMENT_SUCCESS, ENFORCEMENT_NO_LOCAL_USER )); } } @@ -909,9 +941,7 @@ $binder = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('{b // not wear one's face: the operator who reads "reconcile failed" reaches for // elevation and permissions, when the rules are in fact already in place and // only the observation was cut short. - match snapshot(&mut execute) - .and_then(|state| decide(&state, &expected, port)) - { + match snapshot(&mut execute).and_then(|state| decide(&state, &expected, port)) { Ok(true) => Ok(()), Ok(false) => Err(mismatch_message(&want)), Err(error) => Err(unverified_after_write(&error)), @@ -1029,13 +1059,14 @@ mod tests { "active": [ {"name": RULE_NAME_TAILNET, "program": "Any", "ports": ["29470"], "profile": "Any", "remotes": ["100.64.0.0/10"], "hygiene": true, - "enforcement": [1], "sourceType": "Local"}, + "enforcement": [1], "primaryStatus": 1, "sourceType": "Local"}, {"name": RULE_NAME_LAN, "program": "Any", "ports": ["29470"], "profile": "Private,Domain", "remotes": ["192.168.1.0/24"], - "hygiene": true, "enforcement": [1], "sourceType": "Local"} + "hygiene": true, "enforcement": [5, 1], "primaryStatus": 1, "sourceType": "Local"} ], "addresses": addresses - }).to_string(); + }) + .to_string(); let mut written = false; let mut queries = 0; let result = reconcile_with(Path::new(r"C:\spt\spt.exe"), 29470, |leg, _| { @@ -1044,9 +1075,12 @@ mod tests { queries += 1; if queries == 1 { // Exceeds the shipped 3 s probe cap even on a quiet host. - powershell(leg, &format!( - "Start-Sleep -Milliseconds 3200; [Console]::Write('{before}')" - )) + powershell( + leg, + &format!( + "Start-Sleep -Milliseconds 3200; [Console]::Write('{before}')" + ), + ) } else if written { Ok(after.clone()) } else { @@ -1060,9 +1094,15 @@ mod tests { other => panic!("unexpected firewall leg {other}"), } }); - assert!(result.is_ok(), "a completed slow census must reach admission: {result:?}"); + assert!( + result.is_ok(), + "a completed slow census must reach admission: {result:?}" + ); assert!(written, "the absent admission pair must be written"); - assert_eq!(queries, 2, "one pre-write census and a fresh post-write verification"); + assert_eq!( + queries, 2, + "one pre-write census and a fresh post-write verification" + ); } /// The binder is the broker's captured image; anything that is not an absolute, @@ -1104,15 +1144,24 @@ mod tests { "Assert-Owned", "Remove-Owned", ] { - assert!(composed.contains(required), "composed script is missing {required:?}"); + assert!( + composed.contains(required), + "composed script is missing {required:?}" + ); } let bytes = base64::engine::general_purpose::STANDARD .decode(encoded(&composed)) .expect("the encoder must emit valid base64"); - assert_eq!(bytes.len(), composed.encode_utf16().count() * 2, "payload must be UTF-16LE"); + assert_eq!( + bytes.len(), + composed.encode_utf16().count() * 2, + "payload must be UTF-16LE" + ); let decoded: String = char::decode_utf16( - bytes.chunks_exact(2).map(|pair| u16::from_le_bytes([pair[0], pair[1]])), + bytes + .chunks_exact(2) + .map(|pair| u16::from_le_bytes([pair[0], pair[1]])), ) .collect::>() .expect("the payload must decode as UTF-16"); @@ -1157,6 +1206,7 @@ mod tests { remotes: remotes.iter().map(|r| r.to_string()).collect(), hygiene: true, enforcement: vec![ENFORCEMENT_SUCCESS], + primary_status: PRIMARY_STATUS_OK, source_type: "Local".to_string(), } } @@ -1175,8 +1225,17 @@ mod tests { assert_ne!(want.port, CONFIGURED); let on_bound = observed(want.name, BOUND, "", &want.profile, DESIRED_TAILNET_REMOTES); - let on_stale = observed(want.name, CONFIGURED, "", &want.profile, DESIRED_TAILNET_REMOTES); - assert!(spec_satisfied_by(&on_bound, want), "the bound-port rule satisfies the spec"); + let on_stale = observed( + want.name, + CONFIGURED, + "", + &want.profile, + DESIRED_TAILNET_REMOTES, + ); + assert!( + spec_satisfied_by(&on_bound, want), + "the bound-port rule satisfies the spec" + ); assert!( !spec_satisfied_by(&on_stale, want), "a rule sitting on the configured port must NOT satisfy a spec for the bound one" @@ -1195,10 +1254,19 @@ mod tests { #[test] fn a_program_bearing_rule_does_not_satisfy_a_spec_that_wants_no_program_filter() { let want = &desired_specs("c:\\spt\\spt.exe", 5470, &one_lan())[0]; - assert!(want.program.is_none(), "the working default carries no program filter"); + assert!( + want.program.is_none(), + "the working default carries no program filter" + ); let without = observed(want.name, 5470, "", &want.profile, DESIRED_TAILNET_REMOTES); - let with = observed(want.name, 5470, "C:\\spt\\spt.exe", &want.profile, DESIRED_TAILNET_REMOTES); + let with = observed( + want.name, + 5470, + "C:\\spt\\spt.exe", + &want.profile, + DESIRED_TAILNET_REMOTES, + ); assert!(spec_satisfied_by(&without, want)); assert!( @@ -1213,8 +1281,14 @@ mod tests { program: Some(crate::firewall::normalize_path("C:\\spt\\spt.exe")), ..want.clone() }; - assert!(spec_satisfied_by(&with, &program_spec), "a program spec is satisfiable"); - assert!(!spec_satisfied_by(&without, &program_spec), "and is not satisfied by its absence"); + assert!( + spec_satisfied_by(&with, &program_spec), + "a program spec is satisfiable" + ); + assert!( + !spec_satisfied_by(&without, &program_spec), + "and is not satisfied by its absence" + ); } /// u2(i) (re-targeted) — THE SPEC'S REMOTE AND PROFILE ARE COMPARED, AND @@ -1227,7 +1301,10 @@ mod tests { let unrestricted = observed(want.name, 5470, "", &want.profile, &["Any"]); let narrowed = observed(want.name, 5470, "", &want.profile, DESIRED_TAILNET_REMOTES); - assert!(!spec_satisfied_by(&unrestricted, want), "`Any` is not the narrow scope"); + assert!( + !spec_satisfied_by(&unrestricted, want), + "`Any` is not the narrow scope" + ); assert!(spec_satisfied_by(&narrowed, want)); // Order is not significance: the comparison is set-like, so a reordered @@ -1242,13 +1319,25 @@ mod tests { remotes: vec!["192.168.1.0/24".into(), "100.64.0.0/10".into()], ..want.clone() }; - let seen = observed(want.name, 5470, "", &want.profile, &["100.64.0.0/10", "192.168.1.0/24"]); - assert!(spec_satisfied_by(&seen, &reordered), "render order must not decide the verdict"); + let seen = observed( + want.name, + 5470, + "", + &want.profile, + &["100.64.0.0/10", "192.168.1.0/24"], + ); + assert!( + spec_satisfied_by(&seen, &reordered), + "render order must not decide the verdict" + ); // A wrong profile fails even with the right remotes — the trap that a // RemoteIP-only assertion cannot catch. let wrong_profile = observed(want.name, 5470, "", "Private", DESIRED_TAILNET_REMOTES); - assert!(!spec_satisfied_by(&wrong_profile, want), "profile is part of the policy"); + assert!( + !spec_satisfied_by(&wrong_profile, want), + "profile is part of the policy" + ); } /// HYGIENE IS A PRECONDITION, NOT PART OF THE POLICY: a rule that fails it @@ -1258,7 +1347,10 @@ mod tests { fn a_rule_failing_hygiene_is_refused_even_when_its_scope_is_exactly_right() { let want = &desired_specs("c:\\spt\\spt.exe", 5470, &one_lan())[0]; let mut rule = observed(want.name, 5470, "", &want.profile, DESIRED_TAILNET_REMOTES); - assert!(spec_satisfied_by(&rule, want), "the scope is right to begin with"); + assert!( + spec_satisfied_by(&rule, want), + "the scope is right to begin with" + ); rule.hygiene = false; assert!(!spec_satisfied_by(&rule, want)); } @@ -1289,7 +1381,10 @@ mod tests { // A profile set: our render order without a space, NetSecurity's with one. assert_ne!("Domain, Private", DESIRED_LAN_PROFILE, "different strings"); - assert_eq!(profile_set("Domain, Private"), profile_set(DESIRED_LAN_PROFILE)); + assert_eq!( + profile_set("Domain, Private"), + profile_set(DESIRED_LAN_PROFILE) + ); // A network: prefix form as `lan_scope` emits it, mask form as // NetSecurity renders it back. @@ -1307,10 +1402,15 @@ mod tests { } // Host bits are not a second network, and order is not significance. - assert_eq!(ipv4_network("192.168.1.81/24"), ipv4_network("192.168.1.0/24")); + assert_eq!( + ipv4_network("192.168.1.81/24"), + ipv4_network("192.168.1.0/24") + ); let written = vec!["100.64.0.0/10".to_string(), "192.168.1.0/24".to_string()]; - let read_back = - vec!["192.168.1.0/255.255.255.0".to_string(), "100.64.0.0/255.192.0.0".to_string()]; + let read_back = vec![ + "192.168.1.0/255.255.255.0".to_string(), + "100.64.0.0/255.192.0.0".to_string(), + ]; assert_eq!(remote_set(&written), remote_set(&read_back)); } @@ -1327,20 +1427,23 @@ mod tests { let written = vec!["192.168.1.0/24".to_string()]; // POSITIVE CONTROL: the equal case is equal, so every `assert_ne` below // is about the perturbation and not about a comparison that never matches. - assert_eq!(remote_set(&written), remote_set(&["192.168.1.0/255.255.255.0".to_string()])); + assert_eq!( + remote_set(&written), + remote_set(&["192.168.1.0/255.255.255.0".to_string()]) + ); for different in [ - "192.168.1.99", // a single host inside the prefix: narrower - "192.168.1.0/25", // half of it - "192.168.0.0/16", // wider - "0.0.0.0/0", // the whole internet - "Any", // unrestricted, and not a network at all - "192.168.2.0/24", // a different network entirely - "192.168.1.0/33", // malformed: prefix out of range - "192.168.1.0/", // malformed: no length - "192.168.1.0/-1", // malformed: negative length - "192.168.1", // malformed: not four octets - "192.168.1.256/24", // malformed: octet out of range + "192.168.1.99", // a single host inside the prefix: narrower + "192.168.1.0/25", // half of it + "192.168.0.0/16", // wider + "0.0.0.0/0", // the whole internet + "Any", // unrestricted, and not a network at all + "192.168.2.0/24", // a different network entirely + "192.168.1.0/33", // malformed: prefix out of range + "192.168.1.0/", // malformed: no length + "192.168.1.0/-1", // malformed: negative length + "192.168.1", // malformed: not four octets + "192.168.1.256/24", // malformed: octet out of range "192.168.1.0/255.0.255.0", // malformed: non-contiguous mask "not-an-address", "", @@ -1356,28 +1459,52 @@ mod tests { // satisfy the LAN half, or a Private+Domain rule would be satisfied by // one that also admits the local subnet of a PUBLIC network. assert_ne!(profile_set("Any"), profile_set(DESIRED_LAN_PROFILE)); - assert_ne!(profile_set("Domain, Private, Public"), profile_set(DESIRED_LAN_PROFILE)); - assert_ne!(profile_set("Private"), profile_set(DESIRED_LAN_PROFILE), "a subset is not the set"); + assert_ne!( + profile_set("Domain, Private, Public"), + profile_set(DESIRED_LAN_PROFILE) + ); + assert_ne!( + profile_set("Private"), + profile_set(DESIRED_LAN_PROFILE), + "a subset is not the set" + ); // And `Any` is the ABSENCE of an application filter, never a program // named `Any` that could satisfy a spec wanting one. let want = &desired_specs("c:\\spt\\spt.exe", 5470, &one_lan())[0]; - let program_spec = - RuleSpec { program: Some(crate::firewall::normalize_path("C:\\spt\\spt.exe")), ..want.clone() }; - let unrestricted = observed(want.name, 5470, "Any", &want.profile, DESIRED_TAILNET_REMOTES); - assert!(spec_satisfied_by(&unrestricted, want), "control: it satisfies the spec wanting none"); + let program_spec = RuleSpec { + program: Some(crate::firewall::normalize_path("C:\\spt\\spt.exe")), + ..want.clone() + }; + let unrestricted = observed( + want.name, + 5470, + "Any", + &want.profile, + DESIRED_TAILNET_REMOTES, + ); + assert!( + spec_satisfied_by(&unrestricted, want), + "control: it satisfies the spec wanting none" + ); assert!( !spec_satisfied_by(&unrestricted, &program_spec), "an unfiltered rule must not satisfy a spec that wants a program filter" ); - assert!(!program_unrestricted("C:\\spt\\Any.exe"), "a path ending in Any is still a path"); + assert!( + !program_unrestricted("C:\\spt\\Any.exe"), + "a path ending in Any is still a path" + ); // AND MALFORMED TEXT DOES NOT BECOME ACCEPTABLE BY MATCHING ITSELF // (doyle's boundary, 2026-09-12). The observed side falls back to a // literal so it can be compared at all; the WANTED side must parse, or // two identical unparseable strings would agree and stand in for a scope // neither names. - let spec = RuleSpec { remotes: vec!["not-a-network".to_string()], ..want.clone() }; + let spec = RuleSpec { + remotes: vec!["not-a-network".to_string()], + ..want.clone() + }; let identical = observed(want.name, 5470, "", &want.profile, &["not-a-network"]); assert_eq!( remote_set(&spec.remotes), @@ -1388,7 +1515,11 @@ mod tests { !spec_satisfied_by(&identical, &spec), "a spec whose remotes are not networks is satisfied by nothing, including a rule carrying the identical unparseable text" ); - assert_eq!(wanted_networks(&spec.remotes), None, "and the wanted side is what refuses"); + assert_eq!( + wanted_networks(&spec.remotes), + None, + "and the wanted side is what refuses" + ); assert!( wanted_networks(&want.remotes).is_some(), "control: the real spec's remotes DO parse, so the refusal above is the perturbation and not a function that never parses anything" @@ -1413,7 +1544,10 @@ mod tests { fn a_store_holding_only_the_tailnet_half_is_not_a_reconciled_pair() { let want = desired_specs("c:/spt/spt.exe", 5470, &one_lan()); let full = reconciled_store(&want); - assert!(pair_satisfied_by(&full, &want), "the full pair is reconciled"); + assert!( + pair_satisfied_by(&full, &want), + "the full pair is reconciled" + ); for missing in 0..want.len() { let mut half = full.clone(); @@ -1435,13 +1569,26 @@ mod tests { let (tailnet, lan) = (&want[0], &want[1]); assert_ne!(tailnet.name, lan.name, "the halves are distinct names"); assert_ne!(tailnet.remotes, lan.remotes, "and distinct remote scopes"); - assert_eq!(lan.profile, "Private,Domain", "the LAN half is not an all-profile rule"); - assert_eq!(lan.remotes, vec!["192.168.1.0/24".to_string()], "derived from the host census"); + assert_eq!( + lan.profile, "Private,Domain", + "the LAN half is not an all-profile rule" + ); + assert_eq!( + lan.remotes, + vec!["192.168.1.0/24".to_string()], + "derived from the host census" + ); // Swap the two halves' remotes: every name is present, every scope the // pair wants is present SOMEWHERE, and it is still not reconciled. let swapped = vec![ - observed(tailnet.name, 5470, "", &tailnet.profile, &["192.168.1.0/24"]), + observed( + tailnet.name, + 5470, + "", + &tailnet.profile, + &["192.168.1.0/24"], + ), observed(lan.name, 5470, "", &lan.profile, &["100.64.0.0/10"]), ]; assert!( @@ -1460,7 +1607,10 @@ mod tests { // The happy path: TWO rules in each store, and no ambiguity error. assert_eq!(full.len(), 2, "a reconciled store holds both halves"); - let state = Snapshot { active: full.clone(), addresses: one_lan_census() }; + let state = Snapshot { + active: full.clone(), + addresses: one_lan_census(), + }; assert_eq!( decide(&state, "c:/spt/spt.exe", 5470), Ok(true), @@ -1470,9 +1620,15 @@ mod tests { // The real ambiguity: the SAME name twice. let mut duplicated = full.clone(); duplicated.push(full[0].clone()); - let state = Snapshot { active: duplicated, addresses: one_lan_census() }; + let state = Snapshot { + active: duplicated, + addresses: one_lan_census(), + }; let error = decide(&state, "c:/spt/spt.exe", 5470).expect_err("duplicates are refused"); - assert!(error.contains(want[0].name), "the refusal names the duplicated rule: {error}"); + assert!( + error.contains(want[0].name), + "the refusal names the duplicated rule: {error}" + ); } /// FOLD-3: the derived scope is a NETWORK, not the host's own address, and @@ -1483,15 +1639,43 @@ mod tests { #[test] fn the_lan_scope_is_the_network_prefix_and_every_widening_address_is_excluded() { let census = vec![ - Address { address: "192.168.1.81".into(), prefix_length: 24, address_state: "Preferred".into() }, + Address { + address: "192.168.1.81".into(), + prefix_length: 24, + address_state: "Preferred".into(), + }, // the host's own address on a second LAN, same network as the first row's peer set - Address { address: "10.2.3.4".into(), prefix_length: 8, address_state: "Preferred".into() }, + Address { + address: "10.2.3.4".into(), + prefix_length: 8, + address_state: "Preferred".into(), + }, // EXCLUDED, each for its own reason - Address { address: "127.0.0.1".into(), prefix_length: 8, address_state: "Preferred".into() }, - Address { address: "169.254.7.9".into(), prefix_length: 16, address_state: "Preferred".into() }, - Address { address: "100.98.197.12".into(), prefix_length: 32, address_state: "Preferred".into() }, - Address { address: "192.168.9.9".into(), prefix_length: 24, address_state: "Tentative".into() }, - Address { address: "172.16.0.5".into(), prefix_length: 0, address_state: "Preferred".into() }, + Address { + address: "127.0.0.1".into(), + prefix_length: 8, + address_state: "Preferred".into(), + }, + Address { + address: "169.254.7.9".into(), + prefix_length: 16, + address_state: "Preferred".into(), + }, + Address { + address: "100.98.197.12".into(), + prefix_length: 32, + address_state: "Preferred".into(), + }, + Address { + address: "192.168.9.9".into(), + prefix_length: 24, + address_state: "Tentative".into(), + }, + Address { + address: "172.16.0.5".into(), + prefix_length: 0, + address_state: "Preferred".into(), + }, ]; let scope = lan_scope(&census); assert_eq!( @@ -1500,9 +1684,18 @@ mod tests { "the network address, not the host address; loopback, link-local, CGNAT, non-Preferred and an out-of-range prefix all excluded" ); let rendered = format!("{scope:?}"); - assert!(!rendered.contains("0.0.0.0/0"), "a /0 is never rendered: {rendered}"); - assert!(!rendered.contains("192.168.1.81"), "the host address is not a scope: {rendered}"); - assert!(!rendered.contains("100."), "the tailnet range is the other half's scope: {rendered}"); + assert!( + !rendered.contains("0.0.0.0/0"), + "a /0 is never rendered: {rendered}" + ); + assert!( + !rendered.contains("192.168.1.81"), + "the host address is not a scope: {rendered}" + ); + assert!( + !rendered.contains("100."), + "the tailnet range is the other half's scope: {rendered}" + ); } /// A HOST WITH NO LAN IS A STATE, NOT AN ERROR TO FALL OUT OF. The LAN half is @@ -1513,9 +1706,21 @@ mod tests { #[test] fn a_host_with_no_connected_ipv4_omits_the_lan_half_and_says_so() { let tailnet_only = vec![ - Address { address: "100.98.197.12".into(), prefix_length: 32, address_state: "Preferred".into() }, - Address { address: "127.0.0.1".into(), prefix_length: 8, address_state: "Preferred".into() }, - Address { address: "169.254.1.2".into(), prefix_length: 16, address_state: "Preferred".into() }, + Address { + address: "100.98.197.12".into(), + prefix_length: 32, + address_state: "Preferred".into(), + }, + Address { + address: "127.0.0.1".into(), + prefix_length: 8, + address_state: "Preferred".into(), + }, + Address { + address: "169.254.1.2".into(), + prefix_length: 16, + address_state: "Preferred".into(), + }, ]; assert_eq!(lan_scope(&tailnet_only), LanScope::NoneConnected); @@ -1524,10 +1729,16 @@ mod tests { assert_eq!(want[0].name, RULE_NAME_TAILNET); // The tailnet half is PRESENT and correct, and the verdict is still not true. - let state = Snapshot { active: reconciled_store(&want), addresses: tailnet_only }; + let state = Snapshot { + active: reconciled_store(&want), + addresses: tailnet_only, + }; let error = decide(&state, "c:/spt/spt.exe", 5470) .expect_err("a host that cannot carry the LAN half does not verify"); - assert!(error.contains("no connected IPv4"), "the face names the cause: {error}"); + assert!( + error.contains("no connected IPv4"), + "the face names the cause: {error}" + ); assert!( error.contains("Elevation cannot repair"), "and says what will not fix it, since the retry path offers elevation: {error}" @@ -1550,7 +1761,10 @@ mod tests { let written_on_a = reconciled_store(&want_a); // Same rules, new network: UNVERIFIED, not silently accepted. - let state = Snapshot { active: written_on_a.clone(), addresses: census_b.clone() }; + let state = Snapshot { + active: written_on_a.clone(), + addresses: census_b.clone(), + }; assert_eq!( decide(&state, "c:/spt/spt.exe", 5470), Ok(false), @@ -1582,7 +1796,14 @@ mod tests { let want = desired_specs("c:/spt/spt.exe", 5470, &one_lan()); let full = reconciled_store(&want); assert_eq!( - decide(&Snapshot { active: full.clone(), addresses: one_lan_census() }, "c:/spt/spt.exe", 5470), + decide( + &Snapshot { + active: full.clone(), + addresses: one_lan_census() + }, + "c:/spt/spt.exe", + 5470 + ), Ok(true), "the same pair from the local store verifies" ); @@ -1590,9 +1811,19 @@ mod tests { for source in ["Dynamic", "GroupPolicy", ""] { let mut transient = full.clone(); transient[1].source_type = source.to_string(); - let error = decide(&Snapshot { active: transient, addresses: one_lan_census() }, "c:/spt/spt.exe", 5470) - .expect_err("a non-local source is refused"); - assert!(error.contains(want[1].name), "the refusal names the rule: {error}"); + let error = decide( + &Snapshot { + active: transient, + addresses: one_lan_census(), + }, + "c:/spt/spt.exe", + 5470, + ) + .expect_err("a non-local source is refused"); + assert!( + error.contains(want[1].name), + "the refusal names the rule: {error}" + ); assert!( source.is_empty() || error.contains(source), "the refusal names the observed source: {error}" @@ -1608,7 +1839,10 @@ mod tests { // [unit->REQ-BOOTSTRAP-FIREWALL-VERIFY-ONE-PASS] #[test] fn an_empty_effective_store_does_not_verify() { - let state = Snapshot { active: Vec::new(), addresses: one_lan_census() }; + let state = Snapshot { + active: Vec::new(), + addresses: one_lan_census(), + }; assert_eq!(decide(&state, "c:/spt/spt.exe", 5470), Ok(false)); } @@ -1623,7 +1857,10 @@ mod tests { let mismatch = mismatch_message(&want); let unverified = unverified_after_write("powershell.exe: firewall command timed out"); assert_ne!(mismatch, unverified); - assert!(unverified.contains("WRITTEN"), "it states the write landed: {unverified}"); + assert!( + unverified.contains("WRITTEN"), + "it states the write landed: {unverified}" + ); assert!( unverified.contains("not a refused write"), "and says what it is not, since that is the misreading it exists to stop: {unverified}" @@ -1645,19 +1882,63 @@ mod tests { let want = desired_specs("c:/spt/spt.exe", 5470, &one_lan()); let rendered = render_writes(&want); - assert_eq!(rendered.matches("New-NetFirewallRule").count(), 2, "one call per half"); + assert_eq!( + rendered.matches("New-NetFirewallRule").count(), + 2, + "one call per half" + ); for spec in &want { - assert!(rendered.contains(spec.name), "the render names {}", spec.name); + assert!( + rendered.contains(spec.name), + "the render names {}", + spec.name + ); } - assert!(rendered.contains("-RemoteAddress 100.64.0.0/10"), "the tailnet scope is written"); - assert!(rendered.contains("-RemoteAddress 192.168.1.0/24"), "the derived LAN scope is written"); - assert!(rendered.contains("-Profile Private,Domain"), "the LAN half is profile-split"); + assert!( + rendered.contains("-RemoteAddress 100.64.0.0/10"), + "the tailnet scope is written" + ); + assert!( + rendered.contains("-RemoteAddress 192.168.1.0/24"), + "the derived LAN scope is written" + ); + assert!( + rendered.contains("-Profile Private,Domain"), + "the LAN half is profile-split" + ); assert!( !rendered.contains("-Program"), "the working default carries no program filter, and `Any` is not a substitute" ); } + /// The run-02 scratch control isolates `-Package Any`: adding it to an + /// otherwise identical rule changes OK/[5,1] to Inactive/[5,20]. + /// An unrestricted bootstrap rule must omit that package binding entirely. + // [unit->REQ-WEB-LAN-BOOTSTRAP-FIREWALL] + #[test] + fn unrestricted_rules_omit_the_package_parameter() { + let want = desired_specs("c:/spt/spt.exe", 5470, &one_lan()); + let rendered = render_writes(&want); + let mut calls = rendered.split("New-NetFirewallRule").skip(1); + for spec in &want { + let call = calls + .next() + .expect("each admission half must have a create call"); + assert!( + call.contains(spec.name), + "the assertion must inspect the named half" + ); + assert!( + !call + .split_ascii_whitespace() + .any(|argument| argument == "-Package"), + "{} must not acquire the package restriction measured to disable admission: {call}", + spec.name + ); + } + } + // ---- observed-spelling regression cells (hertz, releases#304 W2) ---- // // Appended onto FOLD-4 (10d18b7f). Boundary agreed with todlando: everything @@ -1701,12 +1982,15 @@ mod tests { // POSITIVE CONTROL: the unperturbed store satisfies. Without it every // assertion below passes equally well on a fixture that never matched. let matching = reconciled_store(&want); - assert!(pair_satisfied_by(&matching, &want), "control: the unperturbed pair must satisfy"); + assert!( + pair_satisfied_by(&matching, &want), + "control: the unperturbed pair must satisfy" + ); for wrong in [ - "192.168.1.99", // narrower: one host inside the derived prefix - "0.0.0.0/0", // wider: the whole internet - "not-an-address" // malformed: no network at all + "192.168.1.99", // narrower: one host inside the derived prefix + "0.0.0.0/0", // wider: the whole internet + "not-an-address", // malformed: no network at all ] { let mut store = matching.clone(); for rule in store.iter_mut().filter(|rule| rule.name == RULE_NAME_LAN) { @@ -1719,23 +2003,46 @@ mod tests { } } - /// H2 — AN UNENFORCED RULE IS STILL REFUSED, AFTER THE PAIR MATCHES. - /// - /// Enforcement is decided at its own `decide` arm, on a field the FOLD-4 - /// normalizers never see, and it returns `Err` rather than `Ok(false)`. No - /// sibling asserts it: the source-store arm has :1136, this one had nothing. - /// It exists so a comparison repair cannot fold enforcement into a WEAKER - /// comparison — a rule that is configured but not in force must stay a LOUD - /// refusal and not a quiet non-match. That intent is unchanged; only its - /// mechanism moved. It once guarded against folding enforcement into STRING - /// handling, because the field was Vec; the field is now Vec and - /// the live risk is equality silently becoming MEMBERSHIP, so the cases below - /// include a repeated success code and a success code beside a non-success one. - /// - /// No case here asserts a NAME for any code. The host that produced the A7 - /// capture rendered its codes as display text and exposed no Values qualifier, - /// so no code-to-name mapping is derivable from it; what makes 1 the success - /// code is the documented specification, asserted nowhere but in the constant. + // [unit->REQ-BOOTSTRAP-FIREWALL-ENFORCEMENT-CODES] + #[test] + fn an_ok_multi_profile_rule_with_codes_5_and_1_is_enforced() { + let want = desired_specs("c:/spt/spt.exe", 29470, &one_lan()); + let mut active = reconciled_store(&want); + active[1].enforcement = vec![5, 1]; + let state = Snapshot { + active, + addresses: one_lan_census(), + }; + assert_eq!(decide(&state, "c:/spt/spt.exe", 29470), Ok(true)); + } + + // [unit->REQ-BOOTSTRAP-FIREWALL-ENFORCEMENT-CODES] + #[test] + fn an_inactive_rule_with_codes_5_and_20_is_refused() { + let want = desired_specs("c:/spt/spt.exe", 29470, &one_lan()); + let mut active = reconciled_store(&want); + active[1].primary_status = 2; + active[1].enforcement = vec![5, 20]; + let state = Snapshot { + active, + addresses: one_lan_census(), + }; + assert!(decide(&state, "c:/spt/spt.exe", 29470).is_err()); + } + + // [unit->REQ-BOOTSTRAP-FIREWALL-ENFORCEMENT-CODES] + #[test] + fn an_ok_rule_with_code_1_is_enforced() { + let want = desired_specs("c:/spt/spt.exe", 29470, &one_lan()); + let state = Snapshot { + active: reconciled_store(&want), + addresses: one_lan_census(), + }; + assert_eq!(decide(&state, "c:/spt/spt.exe", 29470), Ok(true)); + } + + /// A matching rule still needs positive enforcement and no code-20 veto. + /// Null/type faults remain a separate query/deserialization failure. // [unit->REQ-WEB-LAN-BOOTSTRAP-FIREWALL] #[test] fn an_unenforced_rule_is_refused_loudly_after_the_pair_matches() { @@ -1744,41 +2051,34 @@ mod tests { // POSITIVE CONTROL: otherwise a reconciled pair, so each refusal below is // attributable to the ONE field it changes. - let good = Snapshot { active: effective.clone(), addresses: one_lan_census() }; + let good = Snapshot { + active: effective.clone(), + addresses: one_lan_census(), + }; assert_eq!( decide(&good, "c:/spt/spt.exe", 29470), Ok(true), "control: the unperturbed snapshot decides reconciled" ); - // Every case is a REFUSAL, and the list is the requirement's acceptance - // table written as literals: [0], [2], [5], [20], [1,1], [1,5] and []. - // [5,20] is carried beyond that list because two non-success codes is a - // distinct shape from one. The arms that matter after the repair are the - // REPEATED success code and the success code accompanied by a non-success - // one — they fail only if `as_slice() != ENFORCEMENT_CERTIFIED` is ever - // softened into "contains the success code". - // - // WHAT THIS FILE CANNOT COVER, so that it is not read as covered: the - // requirement's null arms and its distinguishable REPRESENTATION FAULT - // live in the PowerShell query, before any JSON exists. Their Rust-side - // shadow is the deserialization cell below — a null element and a null - // field both refuse there; the faults themselves are the - // extraction-boundary exercise's subject. + // An empty or non-success array cannot certify admission. Code 20 vetoes + // even an otherwise positive array; inactive-profile code 5 alone is not success. // [unit->REQ-BOOTSTRAP-FIREWALL-ENFORCEMENT-CODES] for status in [ vec![0u16], vec![2u16], vec![5u16], vec![20u16], - vec![ENFORCEMENT_SUCCESS, ENFORCEMENT_SUCCESS], - vec![ENFORCEMENT_SUCCESS, 5u16], + vec![ENFORCEMENT_SUCCESS, ENFORCEMENT_NO_LOCAL_USER], vec![5u16, 20u16], Vec::new(), ] { let mut unenforced = effective.clone(); unenforced[0].enforcement = status.clone(); - let state = Snapshot { active: unenforced, addresses: one_lan_census() }; + let state = Snapshot { + active: unenforced, + addresses: one_lan_census(), + }; let refusal = decide(&state, "c:/spt/spt.exe", 29470) .expect_err("a rule that is not fully enforced is refused"); assert!( @@ -1787,9 +2087,18 @@ mod tests { (status {status:?}): {refusal}" ); } + let mut inactive = effective; + inactive[0].primary_status = 2; + let state = Snapshot { + active: inactive, + addresses: one_lan_census(), + }; + assert!( + decide(&state, "c:/spt/spt.exe", 29470).is_err(), + "a success code cannot override non-OK PrimaryStatus" + ); } - /// H3 — MALFORMED ENFORCEMENT EVIDENCE FAILS DESERIALIZATION, NOT THE VERDICT. /// /// `enforcement: Vec` is the transport guard: anything the query cannot @@ -1807,7 +2116,7 @@ mod tests { format!( r#"{{"name":"n","program":"Any","ports":["29470"],"profile":"Any", "remotes":["Any"],"hygiene":true,"enforcement":{enforcement}, - "sourceType":"Local"}}"# + "primaryStatus":1,"sourceType":"Local"}}"# ) } @@ -1816,20 +2125,20 @@ mod tests { assert_eq!(ok.enforcement, vec![ENFORCEMENT_SUCCESS]); for bad in [ - r#"["Enforced"]"#, // the field defect, in the spelling the host rendered - r#"["Full"]"#, // the pre-repair spelling the gate used to demand - r#"["1"]"#, // numeric-looking string: a stringly pipeline - "[null]", // a null element - "null", // the whole field null - "[1.5]", // fractional, which a coercing reader would round - "[-1]", // negative - "[65536]", // outside u16 - "[true]", // a boolean arriving as JSON. NOTE THE LIMIT: this cell does - // NOT guard against PowerShell coercion. [int]$true is 1, and - // that conversion happens BEFORE any JSON exists, so what - // reaches here is already the number 1 and parses cleanly. - // Only the extraction-boundary exercise can catch that. - r#"[{"code":1}]"#, // structured rather than scalar + r#"["Enforced"]"#, // the field defect, in the spelling the host rendered + r#"["Full"]"#, // the pre-repair spelling the gate used to demand + r#"["1"]"#, // numeric-looking string: a stringly pipeline + "[null]", // a null element + "null", // the whole field null + "[1.5]", // fractional, which a coercing reader would round + "[-1]", // negative + "[65536]", // outside u16 + "[true]", // a boolean arriving as JSON. NOTE THE LIMIT: this cell does + // NOT guard against PowerShell coercion. [int]$true is 1, and + // that conversion happens BEFORE any JSON exists, so what + // reaches here is already the number 1 and parses cleanly. + // Only the extraction-boundary exercise can catch that. + r#"[{"code":1}]"#, // structured rather than scalar ] { assert!( serde_json::from_str::(&rule_json(bad)).is_err(), @@ -1859,14 +2168,12 @@ mod tests { // filters plus DynamicTarget and the Platform/Owner emptiness checks. Those // cmdlets were never run, so the inputs do not exist. `true` here ISOLATES // the representation axes; it does not assert what the host would report. - // - `enforcement`: SYNTHETIC. The capture reads {ProfileInactive, NoLocalUser} in - // ActiveStore. Those are the host’s ADAPTED DISPLAY NAMES, not codes, and THE - // BOOTSTRAP PAIR’S NUMERIC VALUES REMAIN UNKNOWN: the raw UInt16 1 was measured - // on a SEPARATE, pre-existing 5470 rule, not on either bootstrap rule. So nothing - // here may be read as "the pair really carried 1 and only rendered badly". - // ENFORCEMENT_SUCCESS is used because it keeps these cells about the COMPARISON - // axes; it asserts nothing about what that host would have reported. The captured - // value is a SEPARATE FINDING, reported to doyle, not smuggled into a fixture. + // - `enforcement` and `primary_status`: the September 12 display-name capture + // was not a successful pair. Window 6 subsequently measured its raw [5,20] + // refusal. These comparison fixtures now use OK/[5,1], measured on the + // Private+Domain scratch rule without -Package in run-02 (2026-09-14). + // It is deliberately COMPOSED evidence: not a new capture of these named + // bootstrap rules, and not a measured all-profile/tailnet array. // - the port: SYNTHETIC. Its captured value is not in hand. The port axis is not an // equivalence axis and R5 covers its drift, so the spec's port is used. @@ -1875,13 +2182,14 @@ mod tests { fn captured_tailnet(port: u16) -> Rule { Rule { name: RULE_NAME_TAILNET.to_string(), - program: "Any".to_string(), // captured - ports: vec![port.to_string()], // SYNTHETIC, not captured — see above - profile: "Any".to_string(), // captured - remotes: vec!["100.64.0.0/255.192.0.0".to_string()], // captured — MASK form - hygiene: true, // SYNTHETIC, not captured — see above - enforcement: vec![ENFORCEMENT_SUCCESS], // SYNTHETIC, not captured — see above - source_type: "Local".to_string(), // captured + program: "Any".to_string(), // captured + ports: vec![port.to_string()], // SYNTHETIC, not captured — see above + profile: "Any".to_string(), // captured + remotes: vec!["100.64.0.0/255.192.0.0".to_string()], // captured — MASK form + hygiene: true, // SYNTHETIC, not captured — see above + enforcement: vec![5, ENFORCEMENT_SUCCESS], // run-02 enforced-shape composition + primary_status: PRIMARY_STATUS_OK, + source_type: "Local".to_string(), // captured } } @@ -1890,13 +2198,14 @@ mod tests { fn captured_lan(port: u16) -> Rule { Rule { name: RULE_NAME_LAN.to_string(), - program: "Any".to_string(), // captured - ports: vec![port.to_string()], // SYNTHETIC, not captured — see above - profile: "Domain, Private".to_string(), // captured - remotes: vec!["192.168.1.0/255.255.255.0".to_string()], // captured — MASK form - hygiene: true, // SYNTHETIC, not captured — see above - enforcement: vec![ENFORCEMENT_SUCCESS], // SYNTHETIC, not captured — see above - source_type: "Local".to_string(), // captured + program: "Any".to_string(), // captured + ports: vec![port.to_string()], // SYNTHETIC, not captured — see above + profile: "Domain, Private".to_string(), // captured + remotes: vec!["192.168.1.0/255.255.255.0".to_string()], // captured — MASK form + hygiene: true, // SYNTHETIC, not captured — see above + enforcement: vec![5, ENFORCEMENT_SUCCESS], // run-02 enforced-shape composition + primary_status: PRIMARY_STATUS_OK, + source_type: "Local".to_string(), // captured } } @@ -1937,8 +2246,14 @@ mod tests { #[test] fn a_profile_set_compares_by_membership_not_by_rendering() { let want = desired_specs("c:/spt/spt.exe", 29470, &one_lan()); - let lan_spec = want.iter().find(|spec| spec.name == RULE_NAME_LAN).expect("the LAN half"); - assert_eq!(lan_spec.profile, "Private,Domain", "the spec's own rendering, for contrast"); + let lan_spec = want + .iter() + .find(|spec| spec.name == RULE_NAME_LAN) + .expect("the LAN half"); + assert_eq!( + lan_spec.profile, "Private,Domain", + "the spec's own rendering, for contrast" + ); assert!( spec_satisfied_by(&captured_lan(29470), lan_spec), @@ -1956,7 +2271,11 @@ mod tests { fn an_ipv4_network_compares_by_value_in_prefix_or_mask_form() { let want = desired_specs("c:/spt/spt.exe", 29470, &one_lan()); for spec in &want { - let observed = if spec.name == RULE_NAME_LAN { captured_lan(29470) } else { captured_tailnet(29470) }; + let observed = if spec.name == RULE_NAME_LAN { + captured_lan(29470) + } else { + captured_tailnet(29470) + }; assert!( spec_satisfied_by(&observed, spec), "{} — mask form and prefix form name the same network: spec {:?} vs observed {:?}", @@ -1989,8 +2308,6 @@ mod tests { assert_eq!(decide(&state, "c:/spt/spt.exe", 29470), Ok(true)); } - - /// SCOPE: the WRITE BODY -- what `render_writes` emits. Its sibling cell /// `the_query_body_makes_one_store_pass_and_reads_no_persistent_store` is scoped /// to the QUERY BODY, and the two `PersistentStore` counts are OPPOSITE BY @@ -2046,7 +2363,9 @@ mod tests { // ADJACENCY: between the end of the first create and the start of the // second there is whitespace and nothing else. - let first_end = rendered.find(END).expect("the first create ends in a pipeline to Out-Null") + let first_end = rendered + .find(END) + .expect("the first create ends in a pipeline to Out-Null") + END.len(); let second_start = rendered[first_end..] .find(CALL) @@ -2070,7 +2389,6 @@ mod tests { ); } - // [unit->REQ-BOOTSTRAP-FIREWALL-VERIFY-ONE-PASS] /// SCOPE: the QUERY BODY -- the `QUERY` const alone. Its sibling cell /// `the_write_body_renders_an_adjacent_persistentstore_pair_scoped_to_the_derived_prefix` @@ -2117,5 +2435,4 @@ mod tests { "the rationale comment still names the enumeration FOLD-3 removed" ); } - }