Repository navigation
Embedded parent identity vs. parent reference: no consistency invariant #11
Description
Problem
A hierarchical child entity can express its parent's identity in two independent places, with no mechanism tying them together.
Found in `cic-schema-registry`'s `KubernetesNode` (PR #152 review, `theads/thead16.txt`):
- `binding_surface.addresses[].key_fields`: `backend`/`provider`/`location`/`cluster_id` — the node's address is hierarchically nested under the cluster's address (RESTCONF path: `/clusters/cluster={backend},{provider},{location},{cluster_id}/nodes/node={id}`).
- `config_surface.cluster_ref`: a `cic-reference` field whose value is the parent cluster's own full `logical_id` (`cic:kubernetes:{backend}:{provider}:{location}:{id}`).
These are validated completely independently. Nothing enforces:
```
dereference(cluster_ref).logical_id == compose(backend, provider, location, cluster_id)
```
A node's embedded address could name one cluster while `cluster_ref` names a different one — both schema-valid, mutually inconsistent.
Why this matters
This is the third occurrence of the same pattern class the corpus keeps surfacing: field-level validation alone is insufficient, some claims can only be checked as a relationship between two positions, not within either position alone.
- `cic-primitives#8` (closed, PR feat: item_choice, collection item discriminated union #9): a collection item's key field couldn't vary by variant — solved with `item_choice`, a structural fix.
- `thead12` (`cic-schema-registry`): a key_field's valid value set is conditional on a sibling field — documented as an accepted, unaddressed language gap (no Contract mechanism exists for "this field's contract depends on that field's value").
- This one: an embedded parent identity and a separate reference to the same parent can silently diverge — no Contract or grammar rule spans two field POSITIONS (one in `binding_surface`, one in `config_surface`) to check they resolve to the same entity.
Scope
Not proposing a fix here — this is a scoping issue, like #6 was for `derivation_chain`. Before any primitive/grammar design: is this worth solving at all (given it's the only known occurrence so far — `cic-schema-registry`'s `Subnet`/`NetworkSpace` pair does NOT have this problem, since `Subnet`'s `binding_surface` doesn't embed the network space's key at all, only a `network_space_ref`), and if so, at what layer:
- a new Contract type that can reference another field's resolved value (something like `type: must` but crossing from `binding_surface` into `config_surface`, which today's `must` expressions with relative paths like `../config_surface/x` might already partially support syntactically, but nothing evaluates cross-position identity/reference equality specifically)
- a dedicated "parent consistency" annotation on the reference field itself
- accept it as a documented, unenforced invariant (like several
AdapterContractcorrespondences already are) and leave it to adapter-level validation
Evidence / references
- `cic-schema-registry` `general/kubernetes/kubernetes-node/kubernetes-node.yaml` — `binding_surface.addresses[].key_fields` (backend/provider/location/cluster_id) vs. `config_surface.cluster_ref`
- `cic-schema-registry` `theads/thead16.txt` — full writeup, including why `Subnet`/`NetworkSpace` does NOT exhibit this pattern
- `cic-schema-registry` PR #152 — where this was found (did not introduce it; the `cluster_id`+`cluster_ref` duplication predates it, PR #152 just made the full parent key visible in the child address)
Activity
sinkog commentedon Sep 27, 2026
Proposal (recorded, not implemented — see rationale below)
Surveyed the corpus first (
cic-schema-registry,general/, working copies): this pattern — a child'sbinding_surfaceembedding the full parent key AND a separatecic-referencefield to the same parent — occurs exactly once:KubernetesNode/KubernetesCluster. Every othercic-referenceon the corpus (storage-resource.attached_to,compute-resource.network,subnet.network_space_ref,dhcp-options.network_space_ref,kubernetes-cluster.network_ref,kubernetes-node.compute_ref/network_ref) points to a sibling entity whose key is not embedded in the referrer's own address.The mechanism
A new, optional annotation on a
cic-referencefield:parent_key_fields, naming the referrer's ownkey_fields(not the target's) whose combination composes the same address the reference field's value must resolve to.Two layers, deliberately not conflated
check_grammar.py, a new C18): statically checkable — every name inparent_key_fieldsmust resolve to an existingrole: keyentry in akey_fieldslist somewhere in the same document (not necessarily the same surface —cluster_reflives onconfig_surface, the embedded key onbinding_surface). This requires document-level pre-collection of allkey_fieldsnames before per-node checks run, unlikeitem_choice's purely local walk (feat: item_choice, collection item discriminated union #9). Real, but non-trivial, implementation work.cluster_refvalue actually equals the composed address from its ownbackend/provider/location/cluster_idvalues.check_grammar.pyvalidates schema definitions, never instance data — it can never check this, full stop.parent_key_fieldswould only be a structured signal for whoever does validate instances (or auto-derivescluster_refat write time instead of trusting client input), the same status today'stype: mustexpressions already have (documented expectation, not grammar-evaluated).Why not implementing this now
Introducing a new grammar primitive from a single occurrence is exactly the mistake #6/#134 (
derivation_chain,ProviderCorrespondence) were decided against — a schema designed without a repeated, real usage pattern to generalize from. #8/item_choicewas different: that fixed an active defect already live in the corpus (ref+vlan_idredundancy). This one isn't active —kubernetes-node.yamldoesn't currently violate the invariant, it's just not statically checked.Recorded here as a ready-to-implement plan. If a second occurrence of this pattern shows up anywhere in the corpus, this comment is the starting point — not a from-scratch design exercise.