RETE working memory redesign — typed property indexes (the hard risk) Cluster cProfile (combined_40_ings, v0.5.421) flagged dict.get on _facts as the #1 hot spot: 5.85M calls / 1.26s tottime. The cost was per-call 3-tuple allocation + hashing in the canonical `(uid, fact_type, key) -> WorkingMemoryFact` store, plus Fact wrapper unwrapping at every read. This release adds typed sub-indexes that mirror the property_value and property_type slices of _facts: _property_values: dict[node_uid, dict[name, value]] _property_types : dict[node_uid, dict[name, type_name]] Hot-path reads skip tuple construction and Fact wrapping: has_property(uid, name) — 2x dict.get + `in` get_property_value(uid, name) — 2x dict.get get_property_type(uid, name) — 2x dict.get Maintained alongside _facts in assert_fact, retract_fact, retract_all_for_node, and clear. Contract test (18 cases) pins: - read-side semantics (incl. falsy-value preservation: 0/''/False) - mirror consistency across assert / retract / retract_all / clear - has_property MUST distinguish property_value from property_type - get_fact() compat for non-property fact_types This is the "hard risk" optimization step — touches the core storage contract of WM. Validated by 532/532 green tests including CO2 invariant on test_benchmark_two_origins. Local impact invisible (two_origins volume too low). Expected cluster impact 0.4-0.8s on the 22.4s combined_40_ings baseline. Cluster cProfile after deploy will tell us whether this also reduces the cumtime for upstream callers (alpha_network.evaluate 3.46s, conditions.check 1.82s). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>