@@ -42,6 +42,7 @@ def _write_entry(
4242 created : str = "2026-01-01" ,
4343 updated : str | None = None ,
4444 status : str | None = None ,
45+ fired : int | None = None ,
4546) -> None :
4647 """Create an entry with the supplied frontmatter fields."""
4748 path .parent .mkdir (parents = True , exist_ok = True )
@@ -51,6 +52,8 @@ def _write_entry(
5152 "created" : created ,
5253 "reinforced" : reinforced ,
5354 }
55+ if fired is not None :
56+ fm ["fired" ] = fired
5457 if updated is not None :
5558 fm ["updated" ] = updated
5659 if last_surfaced is not None :
@@ -128,6 +131,97 @@ def test_stamp_last_surfaced_returns_false_for_no_frontmatter(home: Path) -> Non
128131 assert decay .stamp_last_surfaced (p , today_iso = "2026-05-27" ) is False
129132
130133
134+ # ── record_surface (fired counter; Defect 2) ─────────────────────────
135+
136+
137+ def test_record_surface_increments_fired_and_stamps_last_surfaced (home : Path ) -> None :
138+ p = home / "knowledge" / "global" / "foo.md"
139+ _write_entry (p , id_ = "foo" , title = "Foo" , fired = 0 )
140+ ok = decay .record_surface (p , today_iso = "2026-05-27" )
141+ assert ok is True
142+ fm = yaml .safe_load (p .read_text (encoding = "utf-8" ).split ("---" )[1 ])
143+ assert fm ["fired" ] == 1
144+ assert str (fm ["last_surfaced" ]) == "2026-05-27"
145+
146+
147+ def test_record_surface_is_per_event_not_daily_gated (home : Path ) -> None :
148+ """The crux of the invariant: two surface events on the SAME day must
149+ bump ``fired`` twice (unlike ``last_surfaced``, which is daily-idempotent)
150+ so ``fired-helpful`` — which can bump several times a day — never exceeds
151+ ``fired``."""
152+ p = home / "knowledge" / "global" / "foo.md"
153+ _write_entry (p , id_ = "foo" , title = "Foo" , fired = 0 )
154+ decay .record_surface (p , today_iso = "2026-05-27" )
155+ decay .record_surface (p , today_iso = "2026-05-27" )
156+ fm = yaml .safe_load (p .read_text (encoding = "utf-8" ).split ("---" )[1 ])
157+ assert fm ["fired" ] == 2
158+ assert str (fm ["last_surfaced" ]) == "2026-05-27"
159+
160+
161+ def test_record_surface_starts_from_zero_when_field_absent (home : Path ) -> None :
162+ """An entry with no ``fired`` field starts the counter at 1, not crash."""
163+ p = home / "knowledge" / "global" / "foo.md"
164+ _write_entry (p , id_ = "foo" , title = "Foo" ) # no fired field written
165+ assert "fired:" not in p .read_text (encoding = "utf-8" )
166+ decay .record_surface (p , today_iso = "2026-05-27" )
167+ fm = yaml .safe_load (p .read_text (encoding = "utf-8" ).split ("---" )[1 ])
168+ assert fm ["fired" ] == 1
169+
170+
171+ def test_record_surface_coerces_bad_fired_value (home : Path ) -> None :
172+ """A non-int ``fired`` (corrupt frontmatter) is treated as 0, then +1."""
173+ p = home / "knowledge" / "global" / "foo.md"
174+ p .parent .mkdir (parents = True , exist_ok = True )
175+ p .write_text (
176+ "---\n id: foo\n title: Foo\n created: '2026-01-01'\n fired: not-a-number\n ---\n \n body\n "
177+ )
178+ decay .record_surface (p , today_iso = "2026-05-27" )
179+ fm = yaml .safe_load (p .read_text (encoding = "utf-8" ).split ("---" )[1 ])
180+ assert fm ["fired" ] == 1
181+
182+
183+ def test_record_surface_preserves_other_frontmatter (home : Path ) -> None :
184+ p = home / "knowledge" / "global" / "foo.md"
185+ _write_entry (p , id_ = "foo" , title = "Foo" , fired = 4 , reinforced = 3 , last_reinforced = "2026-04-01" )
186+ decay .record_surface (p , today_iso = "2026-05-27" )
187+ fm = yaml .safe_load (p .read_text (encoding = "utf-8" ).split ("---" )[1 ])
188+ assert fm ["id" ] == "foo"
189+ assert fm ["reinforced" ] == 3
190+ assert fm ["last_reinforced" ] == "2026-04-01"
191+ assert fm ["fired" ] == 5
192+
193+
194+ def test_record_surface_returns_false_for_missing_file (home : Path ) -> None :
195+ p = home / "knowledge" / "global" / "nonexistent.md"
196+ assert decay .record_surface (p , today_iso = "2026-05-27" ) is False
197+
198+
199+ def test_record_surface_returns_false_for_no_frontmatter (home : Path ) -> None :
200+ p = home / "knowledge" / "global" / "raw.md"
201+ p .parent .mkdir (parents = True , exist_ok = True )
202+ p .write_text ("# Just a heading\n \n No frontmatter here.\n " )
203+ assert decay .record_surface (p , today_iso = "2026-05-27" ) is False
204+
205+
206+ def test_fired_helpful_never_exceeds_fired_under_surface_semantics (home : Path ) -> None :
207+ """Invariant guard: with N surface events and M<=N helpful bumps in the
208+ same day, ``fired-helpful <= fired`` holds. ``record_surface`` counts each
209+ surface; ``fired-helpful`` (modelled here as a same-day multi-bump) stays
210+ bounded by it."""
211+ p = home / "knowledge" / "global" / "foo.md"
212+ _write_entry (p , id_ = "foo" , title = "Foo" , fired = 0 )
213+ # Three surface events today.
214+ for _ in range (3 ):
215+ decay .record_surface (p , today_iso = "2026-05-27" )
216+ fm = yaml .safe_load (p .read_text (encoding = "utf-8" ).split ("---" )[1 ])
217+ fired = int (fm ["fired" ])
218+ # The Scholar would bump fired-helpful at most once per surfaced turn; the
219+ # denominator (fired) must dominate. Two helpful bumps <= three surfaces.
220+ fired_helpful = 2
221+ assert fired == 3
222+ assert fired_helpful <= fired
223+
224+
131225# ── sweep-state I/O ──────────────────────────────────────────────────
132226
133227
0 commit comments