diff --git a/crude_engine/schemas/interface.yaml b/crude_engine/schemas/interface.yaml
index d7d4d84..a212818 100644
--- a/crude_engine/schemas/interface.yaml
+++ b/crude_engine/schemas/interface.yaml
@@ -65,8 +65,19 @@ methods:
type: upsert
get_ip_addresses:
type: dict
+ # #329: was `defaults: {ipv4: {}}` — a stub for a nested per-interface
+ # ip_table that never got built (needs an Engine primitive, see
+ # hitl-engine-park.md). The audit's own "Management IP" schema comment
+ # (below, on ipv4_address) says this method is a flat scalar dict, not
+ # a real table — confirmed live 2026-09-27: ipv4_address/ipv4_prefix/
+ # ipv4_gateway are already fully wired (SNMP/MOPS always were; SSH as
+ # of this same patch) and were being silently discarded because they
+ # were never referenced in defaults, so nothing was ever gathered on
+ # ANY protocol (confirmed via a live SNMP capture: 0 calls made).
defaults:
- ipv4: {}
+ ipv4_address: ''
+ ipv4_prefix: 0
+ ipv4_gateway: ''
get_interfaces_ip:
schema: get_ip_addresses
set_interface:
diff --git a/crude_engine/wire/netconfig.yaml b/crude_engine/wire/netconfig.yaml
index 7a17075..f29fb3d 100644
--- a/crude_engine/wire/netconfig.yaml
+++ b/crude_engine/wire/netconfig.yaml
@@ -8,6 +8,47 @@ value_maps:
management_protocol:
none: static
local: static
+ # #329: SSH's "show network parms" Subnetmask field is dotted-decimal
+ # text; SNMP/MOPS already give hm2netprefixlength as a plain int (walked
+ # via OID, no conversion needed there). Full 33-entry enumeration of
+ # valid dotted-decimal subnet masks (RFC 4632 CIDR, prefix 0-32) — not a
+ # transform function (regex:/math: can't do multi-octet bit-counting
+ # arithmetic on a single val; no custom Python needed either, this is a
+ # finite well-known lookup, same shape as this file's other value_maps).
+ net_mask_to_prefix:
+ "0.0.0.0": 0
+ "128.0.0.0": 1
+ "192.0.0.0": 2
+ "224.0.0.0": 3
+ "240.0.0.0": 4
+ "248.0.0.0": 5
+ "252.0.0.0": 6
+ "254.0.0.0": 7
+ "255.0.0.0": 8
+ "255.128.0.0": 9
+ "255.192.0.0": 10
+ "255.224.0.0": 11
+ "255.240.0.0": 12
+ "255.248.0.0": 13
+ "255.252.0.0": 14
+ "255.254.0.0": 15
+ "255.255.0.0": 16
+ "255.255.128.0": 17
+ "255.255.192.0": 18
+ "255.255.224.0": 19
+ "255.255.240.0": 20
+ "255.255.248.0": 21
+ "255.255.252.0": 22
+ "255.255.254.0": 23
+ "255.255.255.0": 24
+ "255.255.255.128": 25
+ "255.255.255.192": 26
+ "255.255.255.224": 27
+ "255.255.255.240": 28
+ "255.255.255.248": 29
+ "255.255.255.252": 30
+ "255.255.255.254": 31
+ "255.255.255.255": 32
schemas:
read_netconfig:
type: dict
diff --git a/crude_engine/wire/ssh/netconfig.yaml b/crude_engine/wire/ssh/netconfig.yaml
index 715a375..d6bc87a 100644
--- a/crude_engine/wire/ssh/netconfig.yaml
+++ b/crude_engine/wire/ssh/netconfig.yaml
@@ -16,6 +16,14 @@ attributes:
ssh:
read: {command: "show network parms", field: "Gateway address"}
+ # #329. value_map lives in base wire/netconfig.yaml, not here — load_wire()
+ # only merges the overlay's `attributes:`, never a top-level `value_maps:`
+ # block, so declaring it in this file would have silently never resolved.
+ hm2netprefixlength:
+ sources:
+ ssh:
+ read: {command: "show network parms", field: "Subnetmask", tag: value_map, map: net_mask_to_prefix}
+
hm2netvlanid:
sources:
ssh:
diff --git a/docs/FLOORS_BOARD.md b/docs/FLOORS_BOARD.md
index 08c8744..2579538 100644
--- a/docs/FLOORS_BOARD.md
+++ b/docs/FLOORS_BOARD.md
@@ -9,13 +9,13 @@ Cells from Test-owned `tests/fixtures/floors_provenance.json` (redacted aggregat
**floor_source=`gold`** only when device-proved MOPS exists (`mops=pass` from `tests/release_matrix.json` verdicts). Sanitized `gold_floors.json` alone never sets gold.
-Counts: floor_source gold **75** / 77; offline pass **51**; mops pass **75**; snmp pass **75**; ssh pass **46**.
+Counts: floor_source gold **75** / 77; offline pass **51**; mops pass **75**; snmp pass **75**; ssh pass **47**.
## Coverage (Σ end-game progress)
Effort **Σ Coverage** (`local/agents/diagrams/effort-board.md` + `diagrams/resolution-loop.md`). **Possible** = rows with `floor_source=gold` × `{offline,mops,snmp,ssh}`. **Proven** = those cells = `pass`. HITL exceptions are explicit only — never silent gaps.
-**Rollup: proven `246` / possible `300` (82.0%).** Create→Execute→Resolve thickens this forever; lanes A′/B/C feed it.
+**Rollup: proven `247` / possible `300` (82.3%).** Create→Execute→Resolve thickens this forever; lanes A′/B/C feed it.
### Cell vocabulary
@@ -59,7 +59,7 @@ Schema typed reads (`type:` in `dict, list, list_append`) union `gold_floors.jso
| `get_hidiscovery` | `gold` | `pass` | `pass` | `pass` | `pass` |
| `get_interface_statistics` | `gold` | `untested` | `pass` | `pass` | `untested` |
| `get_interfaces` | `gold` | `pass` | `pass` | `pass` | `untested` |
-| `get_ip_addresses` | `gold` | `pass` | `pass` | `pass` | `untested` |
+| `get_ip_addresses` | `gold` | `pass` | `pass` | `pass` | `pass` |
| `get_ip_restrict` | `gold` | `pass` | `pass` | `pass` | `pass` |
| `get_ip_source_guard_bindings` | `gold` | `untested` | `pass` | `pass` | `pass` |
| `get_ip_source_guard_port` | `gold` | `untested` | `pass` | `pass` | `pass` |
diff --git a/tests/fixtures/floors_provenance.json b/tests/fixtures/floors_provenance.json
index bf561ca..7fc1d20 100644
--- a/tests/fixtures/floors_provenance.json
+++ b/tests/fixtures/floors_provenance.json
@@ -180,7 +180,7 @@
"offline": "pass",
"mops": "pass",
"snmp": "pass",
- "ssh": "untested"
+ "ssh": "pass"
},
"get_ip_restrict": {
"floor_source": "gold",
diff --git a/tests/fixtures/offline_gold/catalogue_inventory_draft.md b/tests/fixtures/offline_gold/catalogue_inventory_draft.md
index 3249442..0411bdb 100644
--- a/tests/fixtures/offline_gold/catalogue_inventory_draft.md
+++ b/tests/fixtures/offline_gold/catalogue_inventory_draft.md
@@ -27,7 +27,7 @@ hints until a method is floored and `config_absent` receipts list them.
| `get_dhcp_snooping` | `config_backed` | sanitized floor; config-backed attrs only |
| `get_gmrp` | `config_backed` | sanitized floor; config-backed attrs only |
| `get_gvrp` | `config_backed` | sanitized floor; config-backed attrs only |
-| `get_ip_addresses` | `config_backed` | sanitized floor; config-backed attrs only (canonical; schema alias `get_interfaces_ip` not floored) |
+| `get_ip_addresses` | `config_backed` | sanitized floor; config-backed attrs only (canonical; schema alias `get_interfaces_ip` not floored). Counted here since #192, but the floor was `{"ipv4": {}}` — vacuously empty, not a real comparison — because `defaults:` never referenced `ipv4_address`/`ipv4_prefix`/`ipv4_gateway`. Fixed #329 (2026-09-27): defaults corrected, `hm2NetStaticGroup` added to the XML sample (TEST-NET-1 placeholder), gold regenerated to 3 real leaves. |
| `get_ip_restrict` | `config_backed` | sanitized floor; config-backed attrs only |
| `get_login_policy` | `config_backed` | sanitized floor; config-backed attrs only |
| `get_loop_protection` | `config_backed` | sanitized floor; config-backed attrs only |
diff --git a/tests/fixtures/offline_gold/config_nvm_sample.xml b/tests/fixtures/offline_gold/config_nvm_sample.xml
index 34e0b75..a4eb271 100644
--- a/tests/fixtures/offline_gold/config_nvm_sample.xml
+++ b/tests/fixtures/offline_gold/config_nvm_sample.xml
@@ -108,6 +108,19 @@
2
2
+
+
+ 1
+ C0 00 02 0A
+ 24
+ 1
+ C0 00 02 01
+
diff --git a/tests/fixtures/offline_gold/gold_floors.json b/tests/fixtures/offline_gold/gold_floors.json
index f437588..921cdc5 100644
--- a/tests/fixtures/offline_gold/gold_floors.json
+++ b/tests/fixtures/offline_gold/gold_floors.json
@@ -348,7 +348,9 @@
}
},
"get_ip_addresses": {
- "ipv4": {}
+ "ipv4_address": "192.0.2.10",
+ "ipv4_prefix": 24,
+ "ipv4_gateway": "192.0.2.1"
},
"get_devsec_status": {
"oper_state": "",