Coverage for netbox_data_import/cable_disclosure.py: 100.00%

281 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-30 09:47 +0000

1# SPDX-License-Identifier: Apache-2.0 

2# SPDX-FileCopyrightText: 2026 Marcin Zieba <marcinpsk@gmail.com> 

3"""Apply live row permissions to cached Cable presentation data.""" 

4 

5from dataclasses import replace 

6from types import MappingProxyType 

7from typing import NamedTuple 

8 

9from .catalog import TargetModuleKey 

10from .field_keys import CABLE_END_KINDS 

11from .plan import ImportPlan, register_diagnostic_display 

12 

13CABLE_ROW = "dcim.cable" 

14CABLE_CLASS_MAPPING_ROW = "netbox_data_import.cableclassmapping" 

15CABLE_SEGMENT_OVERRIDE_ROW = "netbox_data_import.cablesegmentoverride" 

16DEVICE_HIDDEN = "a Device you cannot view" 

17DEVICE_ROW = "dcim.device" 

18DISCLOSURE_SOURCE = "disclosure_source" 

19POLICY_HIDDEN = "a policy you cannot view" 

20POLICY_VISIBLE = "policy_visible" 

21TERMINATION_HIDDEN = "a termination you cannot view" 

22TERMINATION_SOURCES = "termination_sources" 

23# A termination display may also name a Device, when that Device alone can hide the display's facts. 

24TERMINATION_SOURCE_KINDS = (*sorted(CABLE_END_KINDS), DEVICE_ROW) 

25 

26# A segment names its two planned ends apart, so one hidden end leaves the other readable. 

27SEGMENT_END_SOURCES = MappingProxyType({"left": "left_sources", "right": "right_sources"}) 

28# The rows a finding's wording describes; `wording_sources` builds them. 

29WORDING_SOURCES = "wording_sources" 

30# What a finding becomes when its viewer cannot see the rows its WORDING_SOURCES name. 

31FINDING_HIDDEN = "cable.finding_hidden" 

32 

33_REFERENCE_FIELDS = frozenset({"device", "cards", "port", "port_class"}) 

34# These codes replace the source `port` with the NetBox port they resolved. 

35_RESOLVED_PORT_FIELDS = _REFERENCE_FIELDS - {"port"} 

36 

37 

38class _DisplaySchema(NamedTuple): 

39 """The display fields one diagnostic may carry, grouped by the row that authorizes them.""" 

40 

41 public: frozenset 

42 cable: frozenset = frozenset() 

43 policy: frozenset = frozenset() 

44 termination: frozenset = frozenset() 

45 # The wording states facts of ports, so the finding renders only when its WORDING_SOURCES are visible. 

46 wording: bool = False 

47 # The finding carries WORDING_SOURCES only when a harness gives rise to it, and then follows the same rule. 

48 wording_optional: bool = False 

49 

50 

51def _harness_schema(public: frozenset, *, cable: frozenset = frozenset(), termination: frozenset = frozenset()): 

52 """Return the schema of one harness finding, which always names its leg and its parent Interface. 

53 

54 Its wording describes the harness as a whole, so every port of the harness authorizes it (section 6.6). 

55 """ 

56 return _DisplaySchema(public | {"segment_index"}, cable=cable, termination=termination | {"parent"}, wording=True) 

57 

58 

59_DIAGNOSTIC_DISCLOSURE_FIELDS = MappingProxyType( 

60 { 

61 "cable.ambiguous_mapped_peer": _DisplaySchema(_RESOLVED_PORT_FIELDS, termination=frozenset({"port", "peers"})), 

62 "cable.attribute_drift": _DisplaySchema( 

63 frozenset({"segment_index"}), cable=frozenset({"cable", "status", "type", "profile", "label"}) 

64 ), 

65 "cable.cableclass_unmapped": _DisplaySchema(frozenset({"segment_index", "cable_class"})), 

66 FINDING_HIDDEN: _DisplaySchema(frozenset()), 

67 "cable.harness_channel_gap": _harness_schema(frozenset(), termination=frozenset({"missing"})), 

68 "cable.harness_channel_state_invalid": _harness_schema( 

69 frozenset({"channel", "fault"}), termination=frozenset({"port"}) 

70 ), 

71 "cable.harness_leg_conflict": _harness_schema(frozenset({"conflicts"})), 

72 "cable.harness_member_unchannelized": _harness_schema(frozenset({"trace"}), termination=frozenset({"port"})), 

73 "cable.harness_member_unresolved": _harness_schema(frozenset({"trace"})), 

74 "cable.harness_mixed_terminations": _harness_schema(frozenset({"models"})), 

75 "cable.harness_occupied": _harness_schema( 

76 frozenset({"marked_connected"}), cable=frozenset({"cable"}), termination=frozenset({"port"}) 

77 ), 

78 "cable.harness_partial": _harness_schema(frozenset(), termination=frozenset({"missing"})), 

79 "cable.harness_profile_unavailable": _harness_schema(frozenset(), termination=frozenset({"channels"})), 

80 "cable.harness_state": _harness_schema(frozenset(), termination=frozenset({"channels", "legs", "profile"})), 

81 "cable.harness_type_conflict": _harness_schema(frozenset()), 

82 "cable.harness_unsupported": _harness_schema(frozenset({"trace"})), 

83 "cable.incompatible_terminations": _DisplaySchema( 

84 frozenset({"segment_index", "left_field_key", "right_field_key"}), 

85 termination=frozenset({"left_model", "right_model"}), 

86 ), 

87 "cable.media_family_mismatch": _DisplaySchema( 

88 frozenset(), cable=frozenset({"segments"}), policy=frozenset({"segments"}) 

89 ), 

90 "cable.multi_termination_conflict": _DisplaySchema( 

91 frozenset({"segment_index"}), cable=frozenset({"cable"}), termination=frozenset({"port"}) 

92 ), 

93 "cable.pass_through_not_mapped": _DisplaySchema( 

94 _REFERENCE_FIELDS, termination=frozenset({"entry", "exit", "mapped"}) 

95 ), 

96 "cable.pass_through_verified": _DisplaySchema(_REFERENCE_FIELDS, termination=frozenset({"entry", "exit"})), 

97 "cable.permission_denied": _DisplaySchema(_REFERENCE_FIELDS | {"permission"}, cable=frozenset({"cable"})), 

98 "cable.planned_termination_conflict": _DisplaySchema( 

99 frozenset({"segment_index", "competing_trace", "competing_segment"}), 

100 termination=frozenset({"termination"}), 

101 wording_optional=True, 

102 ), 

103 "cable.policy_stale": _DisplaySchema(frozenset({"segment_index", "cable_class"})), 

104 "cable.profile_incompatible": _DisplaySchema(frozenset({"segment_index", "cable_class"})), 

105 "cable.resolved_segment_conflict": _DisplaySchema( 

106 frozenset({"segment_index"}), 

107 policy=frozenset({"cable_type", "cable_profile"}), 

108 termination=frozenset({"terminations"}), 

109 ), 

110 "cable.same_port_continuation": _DisplaySchema(_RESOLVED_PORT_FIELDS, termination=frozenset({"port", "peer"})), 

111 "cable.segment_override_lost": _DisplaySchema( 

112 frozenset({"segment_index"}), policy=frozenset({"cable_type", "cable_profile"}) 

113 ), 

114 "cable.segment_reused": _DisplaySchema( 

115 frozenset({"segment_index"}), cable=frozenset({"cable"}), wording_optional=True 

116 ), 

117 "cable.segment_self_connection": _DisplaySchema( 

118 frozenset({"segment_index", "cable_class"}), termination=frozenset({"termination"}) 

119 ), 

120 "cable.termination_channelized": _DisplaySchema( 

121 frozenset({"segment_index"}), termination=frozenset({"port"}), wording=True 

122 ), 

123 "cable.termination_kind_mismatch": _DisplaySchema( 

124 _REFERENCE_FIELDS | {"claimed_kind", "selected_object_type"}, wording=True 

125 ), 

126 "cable.termination_not_connectable": _DisplaySchema( 

127 frozenset({"segment_index"}), termination=frozenset({"port", "interface_type", "parent"}), wording=True 

128 ), 

129 "cable.termination_occupied": _DisplaySchema( 

130 frozenset({"segment_index", "marked_connected"}), 

131 cable=frozenset({"cable"}), 

132 termination=frozenset({"port"}), 

133 ), 

134 "cable.termination_unresolved": _DisplaySchema(_REFERENCE_FIELDS), 

135 "cable.unsupported_termination_kind": _DisplaySchema(_REFERENCE_FIELDS | {"selected_object_type"}), 

136 } 

137) 

138 

139CABLE_DIAGNOSTIC_FIELDS = MappingProxyType( 

140 { 

141 code: fields.public | fields.cable | fields.policy | fields.termination 

142 for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items() 

143 } 

144) 

145 

146CABLE_DIAGNOSTIC_DISCLOSURES = MappingProxyType( 

147 {code: fields.cable for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items() if fields.cable} 

148) 

149 

150POLICY_DIAGNOSTIC_DISCLOSURES = MappingProxyType( 

151 {code: fields.policy for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items() if fields.policy} 

152) 

153 

154# Every code whose wording states facts of ports, so its display must carry WORDING_SOURCES. 

155WORDING_CODES = frozenset(code for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items() if fields.wording) 

156 

157TERMINATION_DIAGNOSTIC_DISCLOSURES = MappingProxyType( 

158 {code: fields.termination for code, fields in _DIAGNOSTIC_DISCLOSURE_FIELDS.items() if fields.termination} 

159) 

160 

161 

162def disclosure_source(row_kind: str, row_pk: int) -> dict: 

163 """Return the plan-side identity for one row that supplied display text.""" 

164 return {"kind": row_kind, "pk": row_pk} 

165 

166 

167def device_source(device_id: int) -> dict: 

168 """Return the source a display needs when it names one resolved Device.""" 

169 return disclosure_source(DEVICE_ROW, device_id) 

170 

171 

172def segment_end_sources(port: tuple[str, int], device_id: int) -> list: 

173 """Return the rows that authorize one planned segment end, which names its Device and its port.""" 

174 return [disclosure_source(*port), device_source(device_id)] 

175 

176 

177def wording_sources(ends) -> dict: 

178 """Return the sources a finding's wording needs: each ``(port, device_id)`` end, the port followed by its Device.""" 

179 return { 

180 WORDING_SOURCES: [ 

181 source for port, device_id in dict.fromkeys(ends) for source in segment_end_sources(port, device_id) 

182 ] 

183 } 

184 

185 

186def termination_sources(*terminations) -> dict: 

187 """Return the sources a display needs when it names the given resolved terminations.""" 

188 return {TERMINATION_SOURCES: [disclosure_source(label, object_id) for label, object_id in terminations]} 

189 

190 

191def disclosed_cable(cable) -> dict: 

192 """Return the fields a planner may record for one visible Cable.""" 

193 return { 

194 "cable_visible": True, 

195 "cable": str(cable), 

196 DISCLOSURE_SOURCE: disclosure_source(CABLE_ROW, cable.pk), 

197 } 

198 

199 

200def policy_row_kind(row) -> str: 

201 """Return the disclosure kind for one supported Cable policy row.""" 

202 from .models import CableClassMapping, CableSegmentOverride 

203 

204 if isinstance(row, CableClassMapping): 

205 return CABLE_CLASS_MAPPING_ROW 

206 if isinstance(row, CableSegmentOverride): 

207 return CABLE_SEGMENT_OVERRIDE_ROW 

208 raise TypeError(f"Unsupported Cable policy row: {type(row).__name__}") 

209 

210 

211def disclosed_policy(row, visible: bool, display: dict) -> dict: 

212 """Return policy display values only when the planning viewer may read their row.""" 

213 if not visible: 

214 return _redact_policy(display) 

215 return { 

216 **display, 

217 POLICY_VISIBLE: True, 

218 DISCLOSURE_SOURCE: disclosure_source(policy_row_kind(row), row.pk), 

219 } 

220 

221 

222def policy_row_is_disclosed(display: dict, row) -> bool: 

223 """Return whether a presented value names this policy row as its visible source.""" 

224 return ( 

225 display.get(POLICY_VISIBLE) is True 

226 and _source_pk(display.get(DISCLOSURE_SOURCE), policy_row_kind(row)) == row.pk 

227 ) 

228 

229 

230def validate_diagnostic_disclosures(code: str, display: dict) -> None: 

231 """Reject diagnostic display fields that bypass the shared disclosure vocabulary.""" 

232 fields = _DIAGNOSTIC_DISCLOSURE_FIELDS.get(code) 

233 if fields is None: 

234 raise ValueError(f"Diagnostic '{code}' has no registered display schema.") 

235 public, cable, policy, termination, wording, wording_optional = fields 

236 if code == "cable.media_family_mismatch": 

237 _validate_media_segments(display) 

238 return 

239 metadata = set() 

240 if cable: 

241 metadata.update({DISCLOSURE_SOURCE, "cable_visible"}) 

242 if policy: 

243 metadata.update({DISCLOSURE_SOURCE, POLICY_VISIBLE}) 

244 if termination: 

245 metadata.add(TERMINATION_SOURCES) 

246 if not _well_formed_termination_sources(display.get(TERMINATION_SOURCES)): 

247 raise ValueError(f"Diagnostic '{code}' names terminations without their sources.") 

248 if wording or (wording_optional and WORDING_SOURCES in display): 

249 metadata.add(WORDING_SOURCES) 

250 _validate_wording_sources(code, display) 

251 unknown = set(display) - set(public) - set(cable) - set(policy) - set(termination) - metadata 

252 if unknown: 

253 names = ", ".join(sorted(unknown)) 

254 raise ValueError(f"Diagnostic '{code}' has unregistered display fields: {names}.") 

255 _validate_row_flags(code, display, cable, policy) 

256 

257 

258def _validate_wording_sources(code: str, display: dict) -> None: 

259 """Reject wording sources that are not port and Device pairs, or that leave out a port the display names.""" 

260 listed = display.get(WORDING_SOURCES) 

261 sources = list(listed) if isinstance(listed, (list, tuple)) else [] 

262 pairs = list(zip(sources[::2], sources[1::2], strict=False)) 

263 if ( 

264 not pairs 

265 or len(sources) != 2 * len(pairs) 

266 or not all( 

267 _well_formed_termination_sources([port]) 

268 and _source_pk(port, DEVICE_ROW) is None 

269 and _source_pk(device, DEVICE_ROW) is not None 

270 for port, device in pairs 

271 ) 

272 ): 

273 raise ValueError(f"Diagnostic '{code}' states a fact of a port without its sources.") 

274 named = list(display.get(TERMINATION_SOURCES) or ()) 

275 ports = {(port["kind"], port["pk"]) for port, _device in pairs} 

276 if any((source["kind"], source["pk"]) not in ports for source in named if _source_pk(source, DEVICE_ROW) is None): 

277 raise ValueError(f"Diagnostic '{code}' names a port its wording sources leave out.") 

278 

279 

280def _validate_row_flags(code: str, display: dict, cable: frozenset, policy: frozenset) -> None: 

281 """Reject Cable and policy fields whose visibility flag and source disagree.""" 

282 source = display.get(DISCLOSURE_SOURCE) 

283 if set(display) & set(cable) or "cable_visible" in display: 

284 visible = display.get("cable_visible") 

285 if not isinstance(visible, bool): 

286 raise ValueError(f"Diagnostic '{code}' has Cable fields without a visibility flag.") 

287 if visible and _source_pk(source, CABLE_ROW) is None: 

288 raise ValueError(f"Diagnostic '{code}' has visible Cable fields without a Cable source.") 

289 if not visible and source is not None: 

290 raise ValueError(f"Diagnostic '{code}' has a source for a hidden Cable.") 

291 if set(display) & set(policy) or POLICY_VISIBLE in display: 

292 visible = display.get(POLICY_VISIBLE) 

293 if not isinstance(visible, bool): 

294 raise ValueError(f"Diagnostic '{code}' has policy fields without a visibility flag.") 

295 row_kinds = (CABLE_CLASS_MAPPING_ROW, CABLE_SEGMENT_OVERRIDE_ROW) 

296 if visible and not any(_source_pk(source, kind) is not None for kind in row_kinds): 

297 raise ValueError(f"Diagnostic '{code}' has visible policy fields without a policy source.") 

298 if not visible and source is not None: 

299 raise ValueError(f"Diagnostic '{code}' has a source for a hidden policy.") 

300 

301 

302def _validate_media_segments(display: dict) -> None: 

303 """Reject media observations that omit provenance or add unregistered nested fields.""" 

304 if set(display) != {"segments"} or not isinstance(display["segments"], list): 

305 raise ValueError("Diagnostic 'cable.media_family_mismatch' has an invalid display schema.") 

306 base = {"segment_index", "retained", "visible", "origin"} 

307 disclosed = {"cable_type", "family", DISCLOSURE_SOURCE} 

308 for segment in display["segments"]: 

309 if not isinstance(segment, dict) or frozenset(segment) not in {frozenset(base), frozenset(base | disclosed)}: 

310 raise ValueError("Diagnostic 'cable.media_family_mismatch' has invalid segment fields.") 

311 origin = segment.get("origin") 

312 if origin not in {"cable", "policy"} or not isinstance(segment.get("visible"), bool): 

313 raise ValueError("Diagnostic 'cable.media_family_mismatch' has an invalid segment origin.") 

314 if segment["visible"] is False: 

315 if set(segment) != base: 

316 raise ValueError("Diagnostic 'cable.media_family_mismatch' discloses a hidden segment.") 

317 continue 

318 row_kinds = (CABLE_ROW,) if origin == "cable" else (CABLE_CLASS_MAPPING_ROW, CABLE_SEGMENT_OVERRIDE_ROW) 

319 if not any(_source_pk(segment.get(DISCLOSURE_SOURCE), kind) is not None for kind in row_kinds): 

320 raise ValueError("Diagnostic 'cable.media_family_mismatch' has a visible segment without its source.") 

321 

322 

323def _source_pk(value, row_kind: str) -> int | None: 

324 if not isinstance(value, dict) or value.get("kind") != row_kind: 

325 return None 

326 row_pk = value.get("pk") 

327 return row_pk if isinstance(row_pk, int) and not isinstance(row_pk, bool) else None 

328 

329 

330def _source_is_visible(value, row_kinds, visible_row_ids: dict[str, set[int]]) -> bool: 

331 """Return whether one well-formed source names a row visible in its expected kind.""" 

332 return any( 

333 (row_pk := _source_pk(value, row_kind)) is not None and row_pk in visible_row_ids.get(row_kind, ()) 

334 for row_kind in row_kinds 

335 ) 

336 

337 

338def _well_formed_termination_sources(sources) -> bool: 

339 """Return whether *sources* is a nonempty list of sources that each name a Cable End Kind row or its Device.""" 

340 return ( 

341 isinstance(sources, list) 

342 and bool(sources) 

343 and all(any(_source_pk(source, kind) is not None for kind in TERMINATION_SOURCE_KINDS) for source in sources) 

344 ) 

345 

346 

347def _terminations_are_visible(sources, visible_row_ids: dict[str, set[int]]) -> bool: 

348 """Return whether every termination, and every Device, one display names is live and visible.""" 

349 return _well_formed_termination_sources(sources) and all( 

350 _source_is_visible(source, TERMINATION_SOURCE_KINDS, visible_row_ids) for source in sources 

351 ) 

352 

353 

354def _identity_key(identity) -> tuple[str, int] | None: 

355 """Return the Cable End Kind row one plan identity names, or None for any other identity.""" 

356 label, _, object_id = str(identity).rpartition(":") 

357 if label not in CABLE_END_KINDS or not object_id.isdigit(): 

358 return None 

359 return label, int(object_id) 

360 

361 

362def _row_sources(value: dict) -> list: 

363 """Return every row source one display mapping carries.""" 

364 sources = [value.get(DISCLOSURE_SOURCE)] 

365 for key in (TERMINATION_SOURCES, WORDING_SOURCES, *SEGMENT_END_SOURCES.values()): 

366 listed = value.get(key) 

367 # A frozen plan holds a list as a tuple. 

368 if isinstance(listed, (list, tuple)): 

369 sources.extend(listed) 

370 return sources 

371 

372 

373def _end_is_visible(sources, visible_row_ids: dict[str, set[int]]) -> bool: 

374 """Return whether a planned end's port and Device are both live and visible.""" 

375 return ( 

376 isinstance(sources, (list, tuple)) 

377 and len(sources) == 2 

378 and _source_is_visible(sources[0], CABLE_END_KINDS, visible_row_ids) 

379 and _source_is_visible(sources[1], (DEVICE_ROW,), visible_row_ids) 

380 ) 

381 

382 

383def _row_ids(units) -> dict[str, set[int]]: 

384 row_ids: dict[str, set[int]] = { 

385 CABLE_ROW: set(), 

386 CABLE_CLASS_MAPPING_ROW: set(), 

387 CABLE_SEGMENT_OVERRIDE_ROW: set(), 

388 DEVICE_ROW: set(), 

389 **{kind: set() for kind in sorted(CABLE_END_KINDS)}, 

390 } 

391 

392 def collect(value) -> None: 

393 if isinstance(value, dict): 

394 for source in _row_sources(value): 

395 for row_kind, ids in row_ids.items(): 

396 if row_pk := _source_pk(source, row_kind): 

397 ids.add(row_pk) 

398 for child in value.values(): 

399 collect(child) 

400 elif isinstance(value, (list, tuple)): 

401 for child in value: 

402 collect(child) 

403 

404 for unit in units: 

405 collect(unit.display) 

406 for diagnostic in unit.diagnostics: 

407 collect(diagnostic.display) 

408 for identity in diagnostic.identities: 

409 if key := _identity_key(identity): 

410 row_ids[key[0]].add(key[1]) 

411 return row_ids 

412 

413 

414def _redact_cable(display: dict, keys: frozenset[str]) -> dict: 

415 for key in keys: 

416 display.pop(key, None) 

417 display.pop(DISCLOSURE_SOURCE, None) 

418 display["cable_visible"] = False 

419 return display 

420 

421 

422def _redact_policy(display: dict) -> dict: 

423 display = dict(display) 

424 for key in ("cable_type", "cable_profile"): 

425 if key in display: 

426 display[key] = POLICY_HIDDEN 

427 if "policy" in display: 

428 display["policy"] = {} 

429 display.pop(DISCLOSURE_SOURCE, None) 

430 display[POLICY_VISIBLE] = False 

431 return display 

432 

433 

434def _media_segment(segment: dict, visible_row_ids: dict[str, set[int]]) -> dict: 

435 source = segment.get(DISCLOSURE_SOURCE) 

436 origin = segment.get("origin") 

437 row_kinds = ( 

438 (CABLE_ROW,) 

439 if origin == "cable" 

440 else (CABLE_CLASS_MAPPING_ROW, CABLE_SEGMENT_OVERRIDE_ROW) 

441 if origin == "policy" 

442 else () 

443 ) 

444 if segment.get("visible") is True and _source_is_visible(source, row_kinds, visible_row_ids): 

445 return segment 

446 return { 

447 "segment_index": segment["segment_index"], 

448 "retained": segment["retained"], 

449 "visible": False, 

450 "origin": origin if origin in {"cable", "policy"} else "cable", 

451 } 

452 

453 

454def _redact_terminations(display: dict, keys: frozenset[str]) -> dict: 

455 for key in keys & set(display): 

456 display[key] = [] if isinstance(display[key], list) else TERMINATION_HIDDEN 

457 display.pop(TERMINATION_SOURCES, None) 

458 return display 

459 

460 

461def _visible_identities(identities, visible_row_ids: dict[str, set[int]]) -> tuple: 

462 """Return the identities a viewer may read: a hidden or deleted termination drops out.""" 

463 return tuple( 

464 identity 

465 for identity in identities 

466 if (key := _identity_key(identity)) is None or key[1] in visible_row_ids.get(key[0], ()) 

467 ) 

468 

469 

470def _diagnostic(diagnostic, visible_row_ids: dict[str, set[int]]): 

471 display = diagnostic.to_dict()["display"] 

472 identities = _visible_identities(diagnostic.identities, visible_row_ids) 

473 schema = _DIAGNOSTIC_DISCLOSURE_FIELDS.get(diagnostic.code) 

474 if ( 

475 schema is not None 

476 and (schema.wording or (schema.wording_optional and WORDING_SOURCES in display)) 

477 and not _terminations_are_visible(display[WORDING_SOURCES], visible_row_ids) 

478 ): 

479 return replace(diagnostic, code=FINDING_HIDDEN, display={}, identities=identities) 

480 if diagnostic.code == "cable.media_family_mismatch": 

481 display["segments"] = [_media_segment(segment, visible_row_ids) for segment in display["segments"]] 

482 elif ( 

483 (keys := CABLE_DIAGNOSTIC_DISCLOSURES.get(diagnostic.code)) 

484 and display.get("cable_visible") is True 

485 and not _source_is_visible(display.get(DISCLOSURE_SOURCE), (CABLE_ROW,), visible_row_ids) 

486 ): 

487 display = _redact_cable(display, keys) 

488 if ( 

489 diagnostic.code in POLICY_DIAGNOSTIC_DISCLOSURES 

490 and display.get(POLICY_VISIBLE) is True 

491 and not _source_is_visible( 

492 display.get(DISCLOSURE_SOURCE), 

493 (CABLE_CLASS_MAPPING_ROW, CABLE_SEGMENT_OVERRIDE_ROW), 

494 visible_row_ids, 

495 ) 

496 ): 

497 display = _redact_policy(display) 

498 keys = TERMINATION_DIAGNOSTIC_DISCLOSURES.get(diagnostic.code) 

499 if keys and not _terminations_are_visible(display.get(TERMINATION_SOURCES), visible_row_ids): 

500 display = _redact_terminations(display, keys) 

501 return replace(diagnostic, display=display, identities=identities) 

502 

503 

504def _redact_segment_ends(segment: dict, visible_row_ids: dict[str, set[int]]) -> None: 

505 """Hide each end of one planned segment whose port or Device the viewer cannot see.""" 

506 for end, source_key in SEGMENT_END_SOURCES.items(): 

507 if not _end_is_visible(segment.get(source_key), visible_row_ids): 

508 segment.pop(source_key, None) 

509 segment[end] = TERMINATION_HIDDEN 

510 # Being a leg is a fact of the channel child, so it hides with that end. 

511 if segment["breakout_leg"] == end: 

512 segment["breakout_leg"] = "" 

513 

514 

515def _unit(unit, visible_row_ids: dict[str, set[int]]): 

516 display = unit.to_dict()["display"] 

517 trace = display.get("trace") 

518 if trace is not None: 

519 logical = trace.get("logical_cable") 

520 if ( 

521 logical is not None 

522 and logical.get("visible") is True 

523 and not _source_is_visible(logical.get(DISCLOSURE_SOURCE), (CABLE_ROW,), visible_row_ids) 

524 ): 

525 trace["logical_cable"] = {"visible": False, "display": "", "description": "", "tags": []} 

526 for segment in trace.get("segments") or (): 

527 if segment.get(POLICY_VISIBLE) is True and not _source_is_visible( 

528 segment.get(DISCLOSURE_SOURCE), 

529 (CABLE_CLASS_MAPPING_ROW, CABLE_SEGMENT_OVERRIDE_ROW), 

530 visible_row_ids, 

531 ): 

532 segment.pop(DISCLOSURE_SOURCE, None) 

533 segment.update(_redact_policy(segment)) 

534 for policy in trace.get("cable_policies") or (): 

535 if policy.get(POLICY_VISIBLE) is True and not _source_is_visible( 

536 policy.get(DISCLOSURE_SOURCE), (CABLE_CLASS_MAPPING_ROW,), visible_row_ids 

537 ): 

538 policy.pop(DISCLOSURE_SOURCE, None) 

539 policy.update(_redact_policy(policy)) 

540 for question in trace.get("devices") or (): 

541 if question.get("selected") and not _source_is_visible( 

542 question.get(DISCLOSURE_SOURCE), (DEVICE_ROW,), visible_row_ids 

543 ): 

544 question.pop(DISCLOSURE_SOURCE, None) 

545 question["selected"] = DEVICE_HIDDEN 

546 for field in trace.get("terminations") or (): 

547 if field.get("selected") and not _source_is_visible( 

548 field.get(DISCLOSURE_SOURCE), CABLE_END_KINDS, visible_row_ids 

549 ): 

550 field.pop(DISCLOSURE_SOURCE, None) 

551 field.update(selected=TERMINATION_HIDDEN, selected_type="") 

552 for segment in trace.get("segments") or (): 

553 # Only a planned segment names NetBox ports; an unplanned one repeats the source text. 

554 if segment.get("segment_key"): 

555 _redact_segment_ends(segment, visible_row_ids) 

556 diagnostics = tuple(_diagnostic(item, visible_row_ids) for item in unit.diagnostics) 

557 return replace(unit, diagnostics=diagnostics, display=display) 

558 

559 

560def present_units(units, viewer) -> tuple: 

561 """Return presentation copies after one live visibility query per referenced row kind.""" 

562 if viewer is None: 

563 raise TypeError("ReviewWorkspace requires a live viewer.") 

564 units = tuple(units) 

565 from django.apps import apps 

566 

567 row_ids = _row_ids(units) 

568 visible = {} 

569 for row_kind, ids in row_ids.items(): 

570 model = apps.get_model(row_kind) 

571 visible[row_kind] = ( 

572 set(model.objects.restrict(viewer, "view").filter(pk__in=sorted(ids)).values_list("pk", flat=True)) 

573 if ids 

574 else set() 

575 ) 

576 return tuple(_unit(unit, visible) for unit in units) 

577 

578 

579def redact_deleted_cables(plan_data: dict) -> dict: 

580 """Return an execution copy whose deleted Cable rows authorize no stored display text.""" 

581 plan = ImportPlan.from_dict(plan_data) 

582 visible = _row_ids(plan.units) 

583 deleted_ids = { 

584 change.payload["cable_id"] 

585 for unit in plan.units 

586 for change in unit.changes 

587 if change.target_module == "cable" and change.operation == "delete" 

588 } 

589 visible[CABLE_ROW].difference_update(deleted_ids) 

590 units = tuple(_unit(unit, visible) for unit in plan.units) 

591 return replace(plan, units=units).to_dict() 

592 

593 

594def register() -> None: 

595 """Register the Cable display vocabulary, so a cached plan with an unknown Cable display field is refused.""" 

596 register_diagnostic_display(TargetModuleKey.CABLE, validate_diagnostic_disclosures) 

597 

598 

599__all__ = ( 

600 "CABLE_CLASS_MAPPING_ROW", 

601 "CABLE_DIAGNOSTIC_DISCLOSURES", 

602 "CABLE_DIAGNOSTIC_FIELDS", 

603 "CABLE_ROW", 

604 "CABLE_SEGMENT_OVERRIDE_ROW", 

605 "DEVICE_HIDDEN", 

606 "DEVICE_ROW", 

607 "DISCLOSURE_SOURCE", 

608 "FINDING_HIDDEN", 

609 "POLICY_DIAGNOSTIC_DISCLOSURES", 

610 "POLICY_HIDDEN", 

611 "POLICY_VISIBLE", 

612 "SEGMENT_END_SOURCES", 

613 "TERMINATION_DIAGNOSTIC_DISCLOSURES", 

614 "TERMINATION_HIDDEN", 

615 "TERMINATION_SOURCES", 

616 "WORDING_CODES", 

617 "WORDING_SOURCES", 

618 "device_source", 

619 "disclosed_cable", 

620 "disclosed_policy", 

621 "disclosure_source", 

622 "policy_row_is_disclosed", 

623 "present_units", 

624 "redact_deleted_cables", 

625 "register", 

626 "segment_end_sources", 

627 "termination_sources", 

628 "validate_diagnostic_disclosures", 

629 "wording_sources", 

630)