"""Exact binding addresses for E3-BIND. Computes, for each event of a lifetime, which learned slot (if any) that event is allowed to write, and for each semantic question which slot holds the currently authoritative version of the queried binding. Contract (preregistered): * the address carries entity id, attribute/relation type, version and slot id - NEVER the semantic value or the answer; * one slot per (entity, attribute, version); NO slot is ever reused; * slot indices are drawn from a per-lifetime random permutation seeded by the lifetime seed alone, so the index cannot encode the label; * a revert moves the AUTHORITY POINTER to an earlier version's slot; it does not move or rewrite any payload. """ from __future__ import annotations import numpy as np from ..world.schema import ET, Fam N_BIND_SLOTS = 192 # E3-A2: measured max 143 versions/lifetime, p99 139 def binding_plan(lt: dict, n_slots: int = N_BIND_SLOTS) -> dict: """-> per-event write slot, per-question read slot, per-slot exact key.""" events = lt["events"] rng = np.random.default_rng(int(lt["seed"])) # label-independent perm = rng.permutation(n_slots) nxt = 0 write_slot = np.full(len(events), -1, np.int16) read_slot = np.full(len(events), -1, np.int16) slot_ent = np.full(n_slots, -1, np.int16) slot_attr = np.full(n_slots, -1, np.int16) slot_ver = np.full(n_slots, -1, np.int16) active: dict[tuple, int] = {} # (ent, attr) -> slot of active version versions: dict[tuple, list] = {} # (ent, attr) -> [(clock, source, slot)] clock = 0 overflow = 0 def alloc(key, clock, source): nonlocal nxt, overflow if nxt >= n_slots: overflow += 1 return -1 s = int(perm[nxt]); nxt += 1 slot_ent[s] = key[0] % 4096 slot_attr[s] = ATTR_ID.get(key[1], 0) slot_ver[s] = len(versions.get(key, [])) versions.setdefault(key, []).append((clock, source, s)) active[key] = s return s for i, ev in enumerate(events): clock += ev.dt p = ev.payload if ev.etype == ET.OBSERVATION: key = (p["ent"], p["attr"]) write_slot[i] = alloc(key, clock, p.get("source", "")) elif ev.etype == ET.CORRECTION: key = (p["ent"], p["key"]) if p["mode"] == "replace": write_slot[i] = alloc(key, clock, p.get("source", "")) else: # revert: authority pointer only, no payload written anywhere hist = versions.get(key, []) tgt = None if p.get("ref_kind") == "source": for c, src, s in hist: if src == p.get("ref_source"): tgt = s else: for c, src, s in hist: if c == p.get("ref_clock"): tgt = s if tgt is not None: active[key] = tgt elif ev.etype == ET.QUESTION: tk = lt["tickets"][i] if "tickets" in lt else None d_ = tk.data if tk is not None else {} if ev.family == int(Fam.SEM_LATEST): read_slot[i] = active.get((d_.get("ent"), d_.get("attr")), -1) elif ev.family == int(Fam.SEM_2HOP): # ONLY the first hop is addressed; hop 2 stays a learned lookup read_slot[i] = active.get((d_.get("src"), d_.get("rel")), -1) return dict(write_slot=write_slot, read_slot=read_slot, slot_ent=slot_ent, slot_attr=slot_attr, slot_ver=slot_ver, n_used=nxt, overflow=overflow) ATTR_ID = {a: i + 1 for i, a in enumerate(sorted( ["location", "team", "status", "phase", "priority", "destination", "owner", "project", "vault", "access code", "firmware tag", "reading", "quantity", "budget"]))}