The PyArchInit ↔ Extended Matrix loop has had a hole in it from day one. Reading from a PyArchInit database into an EM scene worked (GraphProjector); writing back was either impossible or partial. When Enzo Cocca’s PR #28 added the PostgreSQL/PostGIS backend to PyArchInitImporter and his follow-up PR #29 wired a Sub-3 reverse-export operator into EM Tools, the read+write loop was finally there in shape — but the rapporti column on each US row, the physical stratigraphic relationships (copre, taglia, si appoggia a, …), stayed empty on every reverse export. The EM graph didn’t have a place to keep them.
DP-62 closes that hole. The decision taken in conversation 2026-06-04 was to refuse two tempting wrong answers and pick a third. The first wrong answer — represent physical relationships as yEd GraphML edges — would have polluted the temporal Matrix layer, which is what yEd edges are reserved for in EM, and visually destroyed the auto-layout (yEd cannot have invisible edges). The second wrong answer — keep a verbatim copy of the pyArchInit rapporti packed string as a node attribute — would have given the property graph two sources of truth that drift the moment the user touches anything. The third answer, the one that landed: physical relationships are first-class canonical edges in the s3dgraphy property graph, with one edge type per relation per direction (overlies / is_overlain_by, cuts / is_cut_by, fills / is_filled_by, abuts / is_abutted_by, is_bonded_to symmetric, is_physically_equal_to symmetric). The packed string format is serialisation only, deployed at the GraphML and pyArchInit boundaries so the on-disk byte sequence stays byte-identical with the originating column when the graph in between is unmutated.
The three serialisations cohabit by design. In s3dgraphy memory, the canonical edges are the single source of truth — everything else is derived. In yEd GraphML (EM 1.6 palette), the per-node physical_relationships attribute (key d13) carries the same list-of-lists Python literal that pyArchInit’s us_table.rapporti column uses. In pyArchInit’s column, the byte-identical string. The s3dgraphy GraphML exporter also writes a graph-level JSON side channel _s3d_physical_relations for richer round-trip (per-edge AUTHOR / DOCUMENT attributes the packed string cannot carry); on import that channel wins when present, the per-node packed string is the fallback for hand-authored yEd files and pyArchInit-side bridges that only fill the per-node attribute.
The series shipped in four commits on s3dgraphy_v1.6dev — a934c7b extracting the pyArchInit↔canonical-edge vocabulary into the new public s3dgraphy.sync.rapporti module, c0303e5 moving the dispatcher and parse/serialise APIs into the same module, ce308d3 migrating GraphProjector (read side) to consume parse_rapporti() instead of inline ast.literal_eval + private-constant lookups, 2823095 adding GraphML import + export of the d13 packed string with a fallback that activates when the JSON side channel is absent. The yEd palette files on both src/s3dgraphy/templates/em_palette_template.graphml (1c195f1) and the standalone zalmoxes-laran/ExtendedMatrix repo declare physical_relationships on US-type nodes so authors editing in yEd see it as a normal node attribute, defaulting empty. The EM-tools side picks up everything when the bundled s3dgraphy version is bumped past 2823095; older bundles preserve whatever the PyArchInit DB carries without overwriting it.
The deferred follow-ups deserve a paragraph here because closing the trackers without naming them would lose institutional memory. Explicit reciprocity inference (ensure_reciprocals(graph)) is currently structural: each canonical edge type declares its direction (overlies vs is_overlain_by), so writing the right pair of edges suffices. A runtime inference pass would help for graphs imported with one-sided declarations from non-pyArchInit sources, but the observed user-facing need is zero today. Paradox / cycle / temporal-mismatch detection would be the natural next layer: the temporal subsystem has cycle-detection plumbing but doesn’t fire on the physical sub-graph. The mapping-side passthrough flag from the closed s3dgraphy #14 would let arbitrary DB columns survive the round-trip as node attributes instead of forcing every column into a typed wrapper; the current GraphProjector._propagate_node_uuid_and_us host-side hack covers the immediate need. Shipping the node_uuid backfill migration in the s3dgraphy wheel (closed s3dgraphy #15) would let any consumer — not just EM-tools — handle the missing-column error against legacy databases. The edge-style diversification + transitive reduction regression (closed s3dgraphy #17) collapses the five canonical yEd edge styles to a single dashed for now; visually noisy but doesn’t block real workflows. GraphIngestor inserts new rows instead of UPDATEing by node_uuid (closed s3dgraphy #18) falls back to (sito, us) tuple matching, good-enough until the round-trip identity-matching surfaces a real collision. _synth_BR_* synthesized US nodes after GraphML round-trip (closed s3dgraphy #19) is a byproduct of materialize_continuity whose discipline depends on the user-authored BR story that the EM 1.6 manual is still converging on. None of these are blockers for the v1.6 release; they get fixed when they bite.
Cross-references that future readers will want. DP-61 (EM Mappings Registry & Builder) is the natural place to expose the physical_relations_pyarchinit mapping field-type to the community — the long tail of regional pyArchInit forks and lab-specific schemas will need a public catalogue once the registry ships. EM-blender-tools PR #29 (Sub-3 reverse export, Enzo Cocca) is where the EM Tools UI hangs; DP-62 is what makes the row-level write actually carry the physical relations alongside the basic columns. EM-blender-tools #30 (palette physical_relationships) is the user-facing yEd surface; closed against this DP. s3dgraphy #16 (canonical edges + reciprocity + paradox detection) is the upstream tracker whose Sub-feature A is now complete and Sub-features B+C live in this DP’s notes as deferred. Conversation 2026-06-04 with the maintainer set the architectural decision and is the rationale these comments preserve in writing.
1.6
Not needed — design is set; the lossless round-trip is observable end-to-end
Closes the read+write loop opened by Enzo Cocca's PR #28 (PostgreSQL/PostGIS backend for PyArchInit reads) and PR #29 (Sub-3 reverse export). Lands as a series of four commits on `s3dgraphy_v1.6dev` (`a934c7b` vocabulary extraction → `c0303e5` dispatcher + parse/serialize APIs → `ce308d3` graph_projector migration → `2823095` GraphML d13 import/export), plus the palette commit `1c195f1` and the doc commit `5d2dcba`. Companion CHANGELOG entry on s3dgraphy (`053efbc`) catalogues the series. Closed trackers across the EM galaxy: s3dgraphy #16 (Sub-feature A complete), EM-blender-tools #30 (palette + tests + docs + CHANGELOG, all acceptance criteria met), EM-blender-tools #27 (umbrella tracker for PyArchInit Postgres backend + reverse export). Deliberately deferred refinements, captured here so a future reader knows they exist and why they're not shipping in 1.6: (a) explicit `ensure_reciprocals(graph)` inference pass — currently handled structurally because each canonical edge type declares its direction (overlies vs is_overlain_by), so writing both sides suffices; an explicit inference pass would help for graphs imported with one-sided declarations from non-pyArchInit sources, but no observed user-facing need today; (b) `validate_physical_relationships(graph)` paradox / cycle / temporal-mismatch detection — not in scope of the canonical-edges series; the temporal layer already has cycle-detection plumbing but doesn't fire on the physical sub-graph; (c) passthrough mapping flag from the closed s3dgraphy #14, which would let arbitrary DB columns survive the round-trip as node attributes (workaround in place via `GraphProjector._propagate_node_uuid_and_us`); (d) shipping the `node_uuid` backfill migration in the s3dgraphy wheel (closed s3dgraphy #15, EM-tools stop-gap at `import_operators/pyarchinit_migrate.py` keeps real-export consumers unblocked); (e) edge-style diversification + transitive reduction regression on the GraphML writer pipeline (closed s3dgraphy #17, two of the eight pre-existing test failures, visual-only, no real-workflow breakage); (f) GraphIngestor inserts vs UPDATE-by-node_uuid (closed s3dgraphy #18, falls back to `(sito, us)` matching, good-enough for current EM-tools workflows); (g) `_synth_BR_*` synthesized US nodes after GraphML round-trip (closed s3dgraphy #19, byproduct of `materialize_continuity` transform — revisit once the EM 1.6 BR authoring story is finalised in the manual). Cross-refs: DP-61 (mappings registry) — the registry-side surface for declaring `physical_relations_pyarchinit` field types per mapping; the long tail of regional pyArchInit forks and lab-specific schemas will benefit from a public catalogue once the registry ships. Conversation 2026-06-04 set the architectural decision (edges = temporal, packed = physical, no verbatim on node).