feat: APIC-style write validation, query-target=children, contract shadow mirroring (v0.21.0)

Three fidelity gaps surfaced by one broken TN2 var file (missing firewall
block), all fixed:
- writes: reject malformed fvSubnet/l3extSubnet/vnsRedirectDest ip values
  with an APIC-style 400 before any store mutation (deletes exempt)
- query engine: support query-target=children (direct children, flat
  imdata, root excluded) — was silently returning empty
- deploy mirror: materialize vzFilter/vzEntry, vzBrCP/vzSubj (+ filter and
  service-graph subject bindings) and vnsAbsGraph shadows per target site;
  undeploy removes them

E2E-verified through the real pipeline (aci-py hidden push): a TN2 var
missing the fw block now fails with 'Invalid value "." for property ip of
vnsRedirectDest ... -> 400' and nothing lands; a complete var set builds
both MS tenants with contract shadows + sgt-FW graph binding on both sites.
Suite: 914 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dtzp555-max
2026-07-07 18:11:59 +10:00
co-authored by Claude Fable 5
parent 7fdf31aca9
commit 867c05c9c5
7 changed files with 323 additions and 1 deletions
+17
View File
@@ -197,6 +197,23 @@ def _build_result(
page_items = _paginate(flat, params.page, params.page_size)
return [mo.to_imdata() for mo in page_items], total
# query-target=children (real APIC): scope = the DIRECT children of each
# matched root — root itself excluded — returned as FLAT top-level imdata
# entries; target-subtree-class and query-target-filter apply per child.
# (Was previously unsupported and silently returned empty imdata, which
# reads as "object has no children" — a verification false-negative.)
if params.query_target == "children" and params.rsp_subtree is None:
cls_filter = _subtree_classes(params)
flat_children: list[MO] = []
for root in top_level:
for mo in store.children(root.dn):
if cls_filter is None or mo.class_name in cls_filter:
flat_children.append(mo)
flat_children = [mo for mo in flat_children if pred(mo)]
total = len(flat_children)
page_items = _paginate(flat_children, params.page, params.page_size)
return [mo.to_imdata() for mo in page_items], total
# Modern rsp-subtree=children|full: NESTED children under each matched object.
filtered = [mo for mo in top_level if pred(mo)]
total = len(filtered)