Portable metadata and legacy journal values
Metadata on new events and direct-store nodes must contain finite,
JSON-serializable values. NaN, positive/negative infinity and unsupported
objects raise ValueError("Metadata must contain finite JSON-serializable values")
before the event or its recovery work is written. DuckDB and PostgreSQL apply
the same check. Use an explicit JSON null when missing data is what you mean:
memory.store(
"The measurement was unavailable.",
user_id="alice",
metadata={"latency_ms": None, "source": "probe"},
)Admission makes a JSON snapshot before waiting for its storage lock or connection. Mutating the caller’s nested metadata during that wait cannot change the admitted source. JSON normalization still applies: tuples become arrays, for example. This is an event-admission contract; it does not validate arbitrary low-level graph or table mutations.
Grounded extracted quantities use a portable metadata object:
{
"quantity": {
"value": "12.50",
"unit": "$",
"source_text": "$12.50",
"grounding": "object_decimal_v1"
}
}The decimal stays a string so event, graph, DuckDB, PostgreSQL JSONB, and replay
paths preserve its exact value without binary floating-point conversion. The
unit and source text are verbatim evidence, with only surrounding whitespace
removed. If provider JSON encodes the value as a binary float, the float is
discarded and the exact decimal may be reconstructed only from one supported
token in source_text; ordinary object and evidence grounding must still pass.
Approximation and range cues in the surrounding evidence reject a clipped
exact-looking phrase. When quantity fields are absent or invalid, one verbatim
currency or unit may be recognized from a bounded physical, data and count-unit
lexicon in the grounded object. The unit is not normalized and an unlisted noun
is not inferred as a measure. No unit, currency, plural, or locale conversion
is implied. New extraction records use speech_act_v11 (speech_act_v12 when
enable_speaker_grounding checked them against a named speaker, as described in
docs/ENTITY-IDENTITY.md), and their materialization plans use speech_act_v12; v11 also admits one leading exact measure from a
bounded, user-authored first-person completed action after the same evidence and
quantity checks. Saved v10 through v6 records remain v12, v5 remain v11, v4 remain
v10, v3 remain v9, and v2 remain v8. The quantity representation is unchanged from
grounded_quantities_v6, and older records, plans, and checksums remain
unchanged. A missing policy in a legacy extraction record means
source_passage_v1, and recovery prepares a missing plan under
temporal_validity_v7. Version 12 retains v7 temporal validity and rejects a
first-person attempt or intention when its extracted predicate erases the
non-completed speech act, including a relationship between components named
inside that attempted action.
JSON object keys must remain unambiguous after normalization. A nested Python
mapping such as {1: "first", "1": "second"} would otherwise serialize both
keys as "1", silently losing one value when read. Admission rejects collisions
at any nesting depth with ValueError("Metadata object keys collide after JSON serialization"), before writing an event or work. Unambiguous key conversions
still work; the same key in separate objects is not a collision. Already lost
values in older records cannot be reconstructed by this check.
Older DuckDB packs could admit non-finite metadata. Direct storage could convert such values to null in the initial node snapshot while retaining them in the event; raw ingestion could retain them in both. New validation does not rewrite existing source events or claim to recover values already converted to null.
Lifecycle, reinforcement, organizer-merge and consolidation-retirement journals can preserve special
floats from existing nodes. Their checksummed raw record uses
prme-special-floats-v1 only when needed: the encoded value has explicit null
placeholders, with typed paths recording nan, positive_infinity or
negative_infinity. These paths are outside the typed record root, so ordinary
metadata dictionaries and literal strings such as "NaN" cannot collide with
tags. The entire representation is valid JSON, including inside PostgreSQL JSONB.
Decoders verify paths and reconstruct values without silently normalizing them.
Finite records retain their previous serialized bytes. Existing checksums are verified on retained raw bytes; they are never recalculated from reconstructed values. The special-float encoding preserves numeric kinds, not platform-specific NaN payload bits. Older readers do not understand the new special-float wrapper; use the current journal readers for these records. This encoding does not add a complete historical replay engine.
Source: docs/METADATA.md in PRME at 58caddb1.