ADR-00037: Metadata merge-on-write raises on conflicting values, not silent overwrite or merge¶
Context¶
upsert_pointer, update_concept, and update_edge can all write metadata onto a node/edge that may already carry metadata from an earlier write — by a different concept linking the same Pointer (ADR-00033), or by a different writer (human vs. LLM-suggested) updating the same edge (ADR-00034). A second write's metadata needs a defined merge behaviour against what's already there.
Decision¶
Per-key merge with conflict detection, applied uniformly wherever metadata is written onto an existing node or edge. For each key present in both the existing and new metadata: identical values are a no-op; differing values raise MetadataConflictError (new, in errors.py, mapping to a new metadata_conflict error code, following the existing NotFoundError/FormatUnavailableError pattern) naming the conflicting key(s), rather than silently picking a winner. Keys unique to either side pass through untouched.
Alternatives considered¶
- Silent overwrite (new value always wins) — rejected: a second writer's edit could silently destroy a first writer's metadata (e.g. overwriting
provenance: "human_annotated"withprovenance: "llm_discovered"for the same key) with no visibility into what was lost. - Silent merge (new value wins per-key, no error) — has the identical silent-loss problem as overwrite for any key both sides set; rejected for the same reason.
- Whole-dict replace (new
metadataentirely replaces old, no merge at all) — rejected: would force every writer to first read the existing metadata and republish the union just to add one key, defeating the point of an open, incrementally-extensible metadata bag.
Consequences¶
A caller hitting MetadataConflictError must re-issue the write with an explicitly resolved value for the conflicting key(s) — more round trips than silent merge in the conflicting-key case, but no metadata is ever lost without the caller knowing. This is the same shape of tradeoff as any optimistic-concurrency-style conflict signal: correctness over convenience.