add({metadata: {confidence: 0.8}}) lifts reserved fields out of the metadata
bag to their canonical top-level entity fields — teaching consumers that the
metadata bag is a valid write path. update({metadata: {confidence: 0.33}})
then silently dropped the same shape: the patch value survived the metadata
merge but was clobbered one expression later by the preserve-existing spread.
No error, no warning, nothing written. A production consumer's confidence-
evolution writes no-oped for weeks before being caught by reading values back.
Fix: update() now normalizes the metadata patch before any enforcement or
persistence logic runs, mirroring add()'s lift exactly:
- confidence, weight, subtype — remapped to the top-level param unless the
caller also passed that param explicitly (top-level wins). The remapped
subtype flows through subtype-pairing enforcement like a top-level one.
- noun, data, createdAt, updatedAt, service, createdBy, _rev — system-managed
or owned by a dedicated param; dropped from the patch with a one-shot
warning naming the correct write path (silent drop was the only wrong
behavior here).
Five regression tests pin the contract, including the production repro
verbatim (add with metadata.confidence → top-level update → metadata-patch
update → read-back) and the both-paths-supplied precedence case.
1475/1475 unit suite passing.