Closes audit Finding 5 (2026-05-20).
Pre-fix ``CognitiveTurnPipeline._speculative_subjects`` was a bare
``set[str]`` that only grew over a session. Two correctness gaps:
* A subject promoted to ``EpistemicStatus.COHERENT`` via the teaching
review loop kept appearing with the "(speculative, not yet
reviewed)" marker forever, contaminating reviewed material on
later probes.
* Long teaching sessions widened the per-turn substring scan in
``_should_mark_speculative`` without bound.
Fix:
* Back the cache with ``OrderedDict[str, None]`` (LRU) capped at
``_MAX_SPECULATIVE_SUBJECTS = 64``.
* Introduce ``_remember_speculative_subject`` (insert / refresh) and
``_forget_speculative_subject`` (evict) helpers; route all
SPECULATIVE inserts through them.
* When a proposal lands as ``EpistemicStatus.COHERENT``, evict the
subject and every long-enough non-stopword token derived from it,
so the marker stops appearing on reviewed material.
Iteration order in ``_should_mark_speculative`` is unchanged (keys
view); lookups remain O(1). No surface change for any case the prior
behavior didn't already mishandle, so byte-identical eval surfaces
stay stable (verified locally against ``core eval cognition`` public /
holdout / dev splits — all unchanged from MEMORY baseline).
Tests (7 new, ``tests/test_speculative_subject_lifecycle.py``):
* storage is an OrderedDict and the cap is 64
* remember normalizes (lower+strip) and drops empty input
* remember refreshes LRU position on re-insert
* cache caps at 64 with insertion-order eviction
* forget is case-insensitive and removes the entry
* forget on a missing / empty subject is a no-op
* ``_should_mark_speculative`` triggers after remember and stops
triggering after forget
Audit findings referenced:
https://github.com/AssetOverflow/core/pull/76 (Finding 5, "Unbounded
``_speculative_subjects``")