You're right, and thanks for running it against the wheel. After a recall, every new turn is encoded on the mood engine first and gated just imports it, so the leak always lands in gated, including when the recall ran in gated mode. Both of your mechanisms hold: the reverse links that encode() writes stay on the mood side, and the tag is built from the mood engine's state. Two more fields go the same way: momentum, and the strengths of the new memory's forward links, which are scored against the mood store. core_affect doesn't leak. Our turns were different from yours, so our values are too, but the pattern is the same: gated's new tags equal mood's, and gated's running state equals the reference.
We see two things differently. First, in our runs the snapshot doesn't outweigh the links. Repairing only the links or only the tags left a similar number of top-3 changes: in a small store the margins are tiny, so even a weak s6 can flip an order. Second, "Compare both modes" is fair between its two arms, because both copies come from one engine. But with the radio on "Query-gated", that engine is the gated one, so before the fix Compare was reading the leaky store.
On the ids: no. The import was there to keep one appraisal per turn and to stop recall write-backs from crossing over. Matching ids were a side effect, and the missing back-links and the state copied over from the mood engine were an oversight on our side. Nothing in the demo depends on matching ids.
Your swap is now live on the Space. After a recall, gated encodes each new turn itself, with the same appraisal (gated.encode(content, appraisal=memory.tag.appraisal)), and the separate observe is gone, so its state doesn't advance twice. A new test checks tags, back-links and state against a fresh engine fed the same turns, with the recall in either mode. It's a demo-only change, and the fix is yours, so thank you.