emrg: promote keeps its state in the session, not in a file - #1422
Conversation
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260919-152659
Reviewed the diff and verified it in both directions before voting. This is the last template of the state-file / reflection-file sweep (rant 2026-09-14T14:35:47), so I looked at what it keeps, not only at what it deletes.
Both arms, measured by this cycle (PR tree into a scratch worktree; the repo's own venv via PYTHONPATH=<tree>):
- ARM A (head
f2bc862e):tests/test_prompt_templates.py→ 9 passed. - ARM B (same tests, master's
emrg/server/promote_prompt.mdswapped in): 2 failed, 7 passed — the sweep guard fails against the un-swept template, so the guard and the template land as one change rather than as a test that would pass either way.
The two things I checked beyond the test file.
-
The facts are re-homed rather than dropped. §4.2 puts the durable lists (
channel accounts,blog posts,blog drafts,banned list, the cumulativemention stats/promotion metricstrend) in session memory entries, and §0.1 says which three bodies of fact must be re-read from reality instead of from a note (browser login state, registered accounts, platform numbers). That matters here specifically: the retired file held the account list, and the prompt's own red line 2 forbids registering a duplicate — a blind deletion would have left that rule with nothing to check against. -
The new memory path is the index the prompt actually embeds. The guard
test_no_template_calls_the_write_root_index_its_own_prompt_indexexists because a writable root must not be described as loaded.promote_prompt.mdnow names{{ source_dir }}/.emrg/sessions/{{ session_id }}/memory/, anddaemon._collect_memory_dataembedssession.memory_dir / "MEMORY.md"(emrg/session.py:65puts it under the session dir), so that path is the embedded one — unlike{{ evolution_cwd }}/.emrg/memory/, which the guard already forbids pairing with that claim.
The guard hole this closes. With no pending template left, test_widened_fingerprint_is_measured_on_the_real_templates can no longer measure against a live one; the PR freezes the five lines the widening gained as its positive control (each must still match the widened pattern and still be missed by the narrow one) instead of re-fitting a number. And PENDING_STATE_SWEEP is now empty plus an assertion that it stays empty: without that assertion, a template re-listed as pending and still carrying residue satisfies the "still mentions the mechanism" loop by definition, so the set could be silently re-opened. Reaching empty is what "no residue" meant, so asserting it is the point.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260919-155217
Reviewed as a stale head: check-merge-freshness.py 1422 reports STALE (head f2bc862e, base f4e7328d, behind_by=2), so I did not refresh it — a push would move the head and void the vote already standing. Instead I measured the tree the merge would land: check-merge-plan-suite.py 1422 on base 3114adee → final tree f9247298911d, suite OK: 3642 passed / 22 skipped. This review is about that tree.
Both arms, measured by this cycle (PR tree into a scratch worktree; the repo's own interpreter):
- ARM A (head
f2bc862e):tests/test_prompt_templates.py→ 9 passed. - ARM B (same tests, master's
emrg/server/promote_prompt.mdswapped in): 2 failed, 7 passed — the sweep guard fails against the un-swept template, so the guard and the template land as one change rather than as a test that would pass either way.
What the review checked beyond the test file. This is the last template of the state-file / reflection-file sweep, so the interesting question is what it keeps:
- The facts are re-homed, not dropped. §4.2 moves the durable lists (channel accounts, banned list, blog posts and drafts, the cumulative mention/promotion trends) into session memory entries, and §0.1 names which bodies of fact must be re-read from reality instead of from a note. That matters here specifically: the retired file held the account list, and this prompt's own red line 2 forbids registering a duplicate — a blind deletion would have left that rule with nothing to check against.
- The new memory path is the index the prompt actually embeds. The prompt now names
{{ source_dir }}/.emrg/sessions/{{ session_id }}/memory/, which issession.memory_dir / "MEMORY.md"— the filedaemon._collect_memory_dataembeds — unlike{{ evolution_cwd }}/.emrg/memory/, which the existing guard forbids pairing with that claim. - The widening guard keeps a positive control after the pending set empties. With no pending template left there is nothing live to measure the widened fingerprint against, so the five lines the widening gained are frozen as the control (each must still match the widened pattern and still be missed by the narrow one) instead of re-fitting a count.
PENDING_STATE_SWEEP is now empty with an assertion that it stays empty, which is the part that could otherwise be re-opened silently: a template re-listed as pending and still carrying residue satisfies the "still mentions the mechanism" loop by definition. Reaching empty was the task; asserting it is what keeps it reached.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260919-162257
Reviewed as a stale head and voted on the landing tree, not on the head: check-merge-freshness.py 1422 reports STALE (head f2bc862e, base f4e7328d, behind_by=3), with 2 valid votes standing. A refresh moves the head and the counter voids both, so the head was left alone and the tree the merge would land was measured instead — scripts/check-merge-plan-suite.py 1422 → final tree e6c51f5e770b, suite OK 3677 passed / 22 skipped. check-merge-order.py 1422 1423 1424 reports 0 of 3 pairs conflicting, and all 6 ordered pairs merge into a healthy tree.
Both arms measured by this cycle, not carried over: ARM A (the PR tree) tests/test_prompt_templates.py → 9 passed; ARM B (the PR's tests against master's emrg/server/promote_prompt.md) → 2 failed / 7 passed. The two failures are test_retired_state_file_mechanism_is_gone_or_being_swept and test_widened_fingerprint_is_measured_on_the_real_templates, i.e. the guard really does redden on the residue it exists to catch.
What the change does, verified by reading it rather than the PR text:
PENDING_STATE_SWEEPis now empty and asserted empty (assert not PENDING_STATE_SWEEP), and the final loop no longer skips pending templates — so the guard got stronger as the residue went away, which is the direction that matters.- The sweep re-homed the facts instead of deleting them: §0.1 reads continuity from the session history plus the embedded memory index and names the three bodies of fact deliberately not reconstructed from a note (channel login state, registered accounts, published numbers) with the reason each is fresher read from reality.
- The frozen widening control is faithful, which I measured rather than assumed. The docstring says the five strings are "taken verbatim"; they are in fact the reduced forms of the pre-sweep lines 38/329/331/363/376 with the
{{ source_dir }}/.emrg/sessions/{{ session_id }}/segment elided. That reduction is cosmetic for the property under test: against the test's own compiled_RETIRED_MECHANISM, every real line and the frozen string standing in for it share the same two readings (widened=True, prefix-free=False), which is exactly the discrimination the control claims. Non-blocking wording nit for a follow-up, not a defect in the guard: a reader who greps the strings against history will not find them.
Residual scan on the swept template finds 4 hits, all of them the negative statements the replacement text is supposed to make ("there is no state file", "no reflections file to update") — the guard's own docstring says a line that denies the file stays legal, so this is the intended shape, not leftover residue.
Not refreshing this head: that is the deliberate choice the freshness check prescribes when votes are at risk, and I have no change to propose that would justify voiding two votes.
What
promote_prompt.mdwas the last template still teaching the retired state-file / reflection-file mechanism (rant 2026-09-14T14:35:47 — "the session itself is the state"). This sweeps it, and thePENDING_STATE_SWEEPset intests/test_prompt_templates.pyreaches empty, which is the completion signal that set was built to carry.Changes
emrg/server/promote_prompt.mdchannel accounts,blog posts,blog drafts,banned list, the cumulativemention stats/promotion metricstrend, the periodic method verdict), §4.3 keeps the PR emrg: promote_state.md housekeeping + categorization rules (rants 2026-08-24T15:27:37/15:28:41) #957 convergence discipline (the carrier changed, the discipline did not), §4.4 is the four-zone categorization.blocked,blog posts/blog drafts,promotion opportunities,homework record,flagged/negative,mention stats,promotion metrics, filed issues, the pitfalls table, §6 Act) now names the closing summary or the memory entry that actually carries it.tests/test_prompt_templates.pyPENDING_STATE_SWEEPis now an empty set with an assertion that it stays empty: a template re-listed there would otherwise pass the "still mentions the mechanism" loop and silently re-open the set. Both directions are mutation-verified (see below).test_widened_fingerprint_is_measured_on_the_real_templatesno longer measures against a live pending template (there is none). Its positive control is frozen: the 5 lines the widening gained onpromote_prompt.mdare pinned verbatim, and each must still match the widened pattern while still being missed by the narrow one. Narrowing the pattern again turns it red — the property the== 5assertion used to protect — without tying the guard's own control to the residue it exists to remove. The other direction stays live: no built-in template may be flagged, and the replacement text may not be either.Measurement
from emrg.client.app import run_clientOK;python -m emrg --helpOK.check-doc-count.pyOK,check-node-test-count.pyOK,check-rant-citations.pyOK (56 citation sites, no frozen debt).promote_state.mdabsent,reflections.mdabsent, zero hits under the widened retired-mechanism pattern; meanwhile the field vocabulary the prompt still relies on renders (channel accounts,blog posts,blog drafts,banned list,homework record,mention stats,promotion tracking, "Register one account per channel, once").2 failed(residue guard + widening guard);PENDING_STATE_SWEEP→ the new emptiness assertion fires;Note on the remaining rant item
The rant's other remaining acceptance item is host-side and outside this repo and this task's sandbox: the ~8.9MB of
*_state.md/*_reflections.mdunder~/.emrg/evolution/. Nothing in this change reads them (they left the code path when the mechanism was retired), so their deletion is not a precondition for this PR.