Commit Graph
2 Commits
Author SHA1 Message Date
DumiFlex 6917bd9351 feat(api): bilateral tier-2 integrity — reject bundle-child adds on B when parents reference B
The A-side write check (previous commit) prevents building A → B → C from A's perspective, but B can be updated unilaterally. Without a matching check on B's update, B could gain bundle children of its own and silently demote every parent A's reference into a tier-3 structure on read — the resolver would happily attach the new bundle children, the SPA pre-flatten would see two levels of bundles below A, and the tier-2 ceiling would be effectively gone.

Lock the ceiling from both sides:

- _find_parents_referencing scans the library for any bundle whose children contain a bundle ref pointing at the row being updated.
- _validate_parents_compatible runs only when the patch carries a  key AND that new list contains at least one bundle entry — rename / recolor / leaf-only edits skip it entirely.
- 409 Conflict (not 400) because the failure is a state-vs-write conflict, not malformed input. Error body cites the offending parents (up to 3, then '… N more') so the SPA can route the user to detach references first.

5 new tests cover the 409 path, the no-parents allow path, the leaf-only-edit allow path, the metadata-only allow path, and the multi-parent listing in the error message. Full wp_api suite green (174/174).
2026-05-20 17:31:44 +03:00
DumiFlex e3e74fb919 feat(api): tier-2 bundle nesting via references (not snapshots)
Bundles can now contain other bundles as children — but stored as id-only POINTERS, not deep snapshots. The reference model sidesteps the drift cascade that a snapshot model would introduce (parent goes stale every time the inner bundle is edited).

Storage shape for a bundle child:

    { 'id': <ref-id>, 'type': 'bundle', 'name'?: str, 'color'?: str }

Display fields (name, color) are cached so the parent renders a sensible placeholder if the referenced bundle is later deleted; the actual children belong to the referenced row.

Three pieces wired into POST + PUT /wp/api/bundles:

- _normalise_bundle_children strips every field except the reference pointers off bundle-typed children before insert, so a SPA round-tripping the GET-expanded shape doesn't accidentally persist a stale snapshot.
- _validate_bundle_refs rejects three cases against the live repo state: self-include (A → A, PUT only), missing reference (id not in repo), and tier-3 (referenced bundle already contains a bundle child). Tier-2 cap is the architectural ceiling.
- _expand_bundle_children runs on the GET response: every bundle-typed child gets the referenced bundle's CURRENT children attached inline under the same 'children' key, plus _resolved_from / _missing_ref markers. The SPA pre-flatten path (next commit) reads these inline children verbatim — same shape as a snapshot model would have produced, just always fresh.

Headline benefit: an inner-bundle edit shows up in every parent's GET without any propagation pass. No _propagate_bundle_to_bundles, no recursive walk through children blobs. The reference is the source of truth.

11 new tests cover storage stripping, GET expansion, live propagation through reads, tier-2 rejection, missing-ref handling, and mixed leaf/ref children.
2026-05-20 17:24:14 +03:00