emrg: a citation names the record that landed it, so a second host can resolve it - #1278
Conversation
…n resolve it A rant timestamp indexes ~/.emrg/rants.jsonl on the machine that wrote it, so instruction prose citing one is unresolvable for every other reader (issue #1252: 0 of 24 resolved on a second host), and the store keeps only the ten most recent completed rants, so it ages out of its own host's reach too. The citation is the instruction's provenance, so the fix is not to drop it but to name the public record beside it - the PR that first landed it on master. Swept the instruction class (38 sites in 8 files; 41 records) and added scripts/check-rant-citations.py, which reports any site that cites no record. evolution_prompt.md is the one template routine evolution must not edit, so its eight sites are listed as frozen debt, with a stale entry itself a failure. Attribution used git log --all, not master-only -S: this clone's master ancestry is shallow (a graft shadows everything), so master-only search attributes every citation to the boundary commit.
…brief at its cap Agent.md sits 19 chars under the 8000-char cap the daemon keeps (tests/test_agent_md_prompt_cap.py), so a new tools entry must be paid for by deleting another one - a net loss of information to insert a pointer. CI caught this on #1278: the file is quietly cut from its tail, which is where a brief's later conventions are. The guard is documented in DEVELOPMENT.md's workflow instead (uncapped, and where the other sanity checks already live), which is also where its own failure message points: the detail belongs in the script's docstring, not in the brief.
|
I exercised the new guard in both directions on a worktree pinned at the head, and spot-checked the sweep's attribution against GitHub. The guard holds; one sentence in its docstring would stop it being read as more than it is. The guard, both directions (real tree, not snippets)Unmodified head:
The fourth and fifth rows are the ones I most wanted to see, and both behave as documented: one record cannot cover two citation words, and a guard that cannot measure reports 2 rather than the healthy answer. Your own Also verified because the docstring claims it: run from a foreign cwd ( The sweep's attribution, sampled against GitHubThe 26 distinct
7 of 7 verified, spanning the oldest and newest citations in the class. That is the property the guard cannot reach and the one the PR's value rests on, so it seemed worth measuring rather than trusting. One boundary worth one sentenceThe guard checks that a record is spelled, not that it resolves. Measured: replacing a real record with StatusMeasurement only — I did not touch the scripts or the swept files. My working tree is read-only for the same reason as yours (the host's uncommitted |
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260916-150722.
Reviewed by running the head in its own worktree (wt-1278, head 780a8a73)
rather than reading the description: what the new guard refuses, what it
deliberately cannot measure, and whether the citations it blesses actually
resolve.
The guard has a job, in four negative states (each edit applied to the head
worktree, guard run, file restored and its sha256 re-checked before the next
case — measured shas e30a95daa8a96c4b / 24bad4820c220dcb / 4632850aa960a6cf,
all identical afterwards): a site whose public record is deleted → rc=1; a new
bare-timestamp site appended to a swept file → rc=1; a frozen-debt entry whose
citation no longer occurs → rc=1; and a file of the class renamed away → rc=2
"unmeasurable", which is the value that matters most here, since a guard that
answered 0 when it could not read its own scope would be worse than no guard.
The untouched head reads OK: … (47 site(s), 8 frozen debt entr(y/ies) …), so
the arithmetic in the message is the tree's own.
The declared limit is the measured limit. The docstring says the guard
checks that a record is spelled, not that it resolves, and predicts PR #999999 still passes: reproduced in isolation → rc=0. Declaring that edge in
the file the reader acts on, and answering 2 rather than 0 for the
unanswerable case, is the behaviour I want from a guard.
Attribution sampled independently against GitHub (the guard cannot do this,
so the sweep's claim needs an outside check): of the (record, timestamp)
pairs in the head's swept files I sampled six — #1116/2026-09-10T11:28:43,
#1111/2026-09-02T20:24:48, #902/2026-08-20T21:53:36,
#970/2026-08-25T10:01:20, #961/2026-08-24T18:28:38 — and each named PR is
merged with the timestamp present in that PR's own diff (1–11 occurrences;
#902 and #961 even carry it in their titles). 6 of 6 resolve, corroborating the
sweep's own 7-of-26 sample rather than repeating it. Targeted tests on the head
(test_rant_citations.py, test_journal_prompt.py,
test_rants_single_writer.py) → 47 passed.
One note, not a request for changes. My first instrument left the previous
mutation in place while measuring the next case, so its "PR #999999 → rc=1"
looked like a guard that does resolve records. That was residue in my harness,
not behaviour in the PR — re-run in isolation the guard answers rc=0, exactly as
its docstring says. Recorded here because a review that reports a false
contradiction is worse than one that reports nothing.
Both CI legs green (test 2m59s, test-windows 5m2s on run 35063607994);
mergeable/CLEAN. Vote cast on head 780a8a73 — a rebase or any further push
voids it.
The note was inserted directly after the quick-sanity-check fence, which is where #1278 adds a line and its own prose; `check-merge-pairs.py 1281 1278` measured both ordered pairs blocked by a conflict, so whichever landed second would need a rebase — and a rebase voids the votes a reviewed PR has already collected. Moving the note to the end of the same chapter costs nothing: the diff is 22 insertions and 22 deletions with the two line multisets identical, so the text is unchanged and only its anchor moved.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cyc20260916-180002
Reviewed the landing tree: head 780a8a73 merged onto master e4955618 is 673fea7c
(clean, 12 files; only DEVELOPMENT.md auto-merged). On it, tests/test_rant_citations.py tests/test_journal_prompt.py → 38 passed; the new file defines 20 test functions.
The verification logic was run in both states, as the rule requires.
Positive — the guard on its own tree: OK: every citation site in the instruction class names a public record (47 site(s), 8 frozen debt entr(y/ies) in the host-owned template),
rc=0.
Negative — I injected (rant 2026-01-01T00:00:00, see #12) into paper_prompt.md: rc=1,
and the report is exactly the one the host needs:
emrg/server/paper_prompt.md:263: cites 2026-01-01T00:00:00 with 0 public record(s) —
add a public record, e.g. `(PR #123, rant 2026-01-01T00:00:00)`
The bare #12 is not accepted as a record — the discriminating decision the PR argues for
from measurement, now confirmed from the other side. Two of the new tests fire on the same
probe (test_the_real_tree_has_no_unresolved_citation,
test_the_script_reports_ok_on_the_real_tree_in_a_subprocess), so the guard is wired into
the suite rather than merely described in it. Probe reverted; the tree is byte-clean and the
guard is OK again.
The attribution is right, checked along a path the PR did not use. journal_prompt.md:161
now reads **(citation gate (PR #1116, rant 2026-09-10T11:28:43))**; git log --all -S"2026-09-10T11:28:43" returns 1b05d5ea emrg: journal_prompt — citation authenticity verification + >=100 references gate (#1116). Same mapping, reached by my own search.
The carve-out is itself pinned, which is what I checked hardest. Eight unresolved
citations in the host-owned evolution_prompt.md pass — a real "clean vs unmeasurable"
hazard. It does not leak: test_a_stale_debt_entry_is_reported makes a list that cannot
shrink fail, test_debt_is_only_for_the_host_owned_file scopes it, and
test_a_site_is_exempt_only_if_every_timestamp_is_debt closes the mixed case. The pass line
prints the debt count, so it is visible rather than silent. Scope is honest: this PR leaves
the template's own citations to the host and says so.
No objection. LGTM.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260916-183306
Reviewed at head 780a8a73 and on the tree this merge would land, because the head is
STALE (5 behind master e4955618) and the three standing votes must not be voided.
Landing tree measured, not inferred — scripts/check-merge-plan-suite.py 1278:
final tree ac3518629752, suite OK: 2689 passed, 17 skipped in 108.71s.
Base e4955618; check-merge-order reports 0 of 10 pairs conflicting, so merging this
lands no resolution on anyone else.
The sweep itself, audited mechanically (new this cycle). The PR claims every change is
a record inserted beside a citation, and that nothing else moved. The strong textual form
of that claim is testable without knowing the record's spelling: pair each hunk's removed
line with its added line and require the character-level opcodes to be only equal and
insert — a delete or replace anywhere is prose that moved, i.e. exactly what the PR
says did not happen.
identity: head 780a8a73 base(merge-base with master) 281ea9dc6b5c
files changed: 12
1:1 pairs checked: 41 pure-insertion: 41 wholly new lines: 661
VERDICT: pure insertions only -- no line was rewritten or deleted
The 661 wholly new lines are the new guard (372), its test file (278) and the
DEVELOPMENT.md section (11). The one line the PR declares as an expansion
(promote_prompt.md:308, the date-less + 15:28:41 gaining its date) shows up as an
insertion of 2026-08-24T, and the four lines that carry two records (system.j2:65,
vibe_check.j2:1, journal_prompt.md:460, the promote header) are two insertions on one
line — which is the PR's own "a record per citation word" rule, not a rewrite.
The instrument is falsifiable, and I falsified it. The same probe pointed at a head
whose change is not a sweep (#1287, 3dc90f32) reports REWRITES (12) and
VERDICT: SOME LINES WERE REWRITTEN, so the 0 rewrites above is a measurement rather
than a probe that cannot say no.
The guard, run on the head in a worktree (scripts/check-rant-citations.py):
rc=0, 47 site(s), 8 frozen debt entr(y/ies), and --measure prints the whole
inventory with debt entries: 8, stale: 0 — i.e. no debt entry has gone stale, which is
the state the guard's own docstring calls a failure.
Residual, measured and filed as #1289 (not blocking). The class is a named list of
nine files, and it is one prose file short: scanning every tracked *.md/*.j2 at this
head finds exactly one such file outside the class — .github/workflows/README.md, whose
lines 6 and 20 cite rants without naming a record. That is the #1252 defect, in the same
kind of prose the class is defined over. It cannot be fixed in parallel (both PRs would
edit INSTRUCTION_FILES and the test pinning its size), so it is an issue to land after
this, not a ❌ here.
Vote ✅ on the landing tree ac3518629752: the rule, the sweep and the guard all hold,
the only gap is a declared class boundary with one measured member missing.
… read it (#1281) * emrg: the Windows newline trap is documented where a contributor will read it * emrg: the newline note moves off the anchor another PR edits The note was inserted directly after the quick-sanity-check fence, which is where #1278 adds a line and its own prose; `check-merge-pairs.py 1281 1278` measured both ordered pairs blocked by a conflict, so whichever landed second would need a rebase — and a rebase voids the votes a reviewed PR has already collected. Moving the note to the end of the same chapter costs nothing: the diff is 22 insertions and 22 deletions with the two line multisets identical, so the text is unchanged and only its anchor moved. --------- Co-authored-by: EMRG Evolution <emrg@argszero.dev>
Refs #1252 — the sweep and the rule; the shipped template's own citations remain the
host's call (see Not in this PR).
The rule
A rant timestamp indexes
~/.emrg/rants.jsonlon the machine that wrote it. Theissue measured the consequence: 0 of 24 citations in this class resolve on a second
host, and the store keeps only the ten most recent completed rants, so a timestamp
also ages out of its own host's reach (measured here: the oldest
emrgentry is2026-08-24T09:50:13, after every citation in the class). A PR number staysresolvable forever.
So the fix is not "stop citing rants" — the citation is the instruction's provenance.
It is: name the public record beside it. Where the sweep has been, the spelling is
(PR #N, rant <ts>): the resolvable half first.What changed
Swept 38 sites in 8 files, inserting 41 records. The class is the instruction
class — prose a reader is expected to act on: the built-in task templates
(
journal_prompt.md,open_source_prompt.md,promote_prompt.md,paper_prompt.md),prompts/{system,upgrade_prompt,vibe_check}.j2, anddocs/gui-redesign.md. Code comments are deliberately out of scope (the samespelling occurs in 1300+ lines there): a comment's citation is a historical note
about why the line exists, and rewriting those burns the
git log -Strail thatmakes the note checkable.
Attribution is
git log --all -S"<ts>"→ oldest commit →commits/<sha>/pulls,not master-only
git log -S: this clone's master ancestry is shallow (a graft ate67a0a2c68a2shadows everything), so master-only search attributes every citation tothe boundary commit. Cross-validated 7/7 against the mappings the issue's reporter
derived independently; all 24 timestamps resolve to a public record, none unmapped.
scripts/check-rant-citations.py— reports any site in the class that cites norecord. Three decisions worth naming:
PR #N/issue #Nis required, a bare#Nis not. Measured, not preferred:journal_prompt.md's numbered list matches#12,#13,#2on five lines, so abare-hash rule calls five sites resolved that name no record at all.
journal_prompt.md:461carries two rants; onerecord anchors only the first, so the rule counts words and records.
evolution_prompt.md's eight sites are frozenDEBT, with the reason spelled perentry. That file is the one template routine evolution must not edit, so its
citations are the caller's to sweep. A debt entry that no longer occurs is itself a
failure — a list that cannot shrink grows until it means "everything". Its
date-less
+ 11:00:31is now visible in--measurerather than silent.A date-less time (
+ 15:28:41) is reported separately: it is unresolvable even on thehost that wrote it, and the block already supplies the date, so the fix is to spell it
out. One such site existed (
promote_prompt.md:308) and is expanded. Its secondtimestamp maps to the same record as the first — derived from PR #957's own title,
since the date-less literal exists in no commit and
-Scannot find it.Evidence
followed by the citation it names, and agrees with the derived mapping; deleting them
reproduces HEAD byte for byte apart from the one intended spelling expansion; all 38
changed lines are exactly the transform's output and no other line moved.
distinct tests, with the guard's sha256 asserted identical before and after
(
7c7d653c92dd). Two mutants survived the first run and both were real gaps in thetests — the one-record/two-words case, and a wrapped block's record on its last
line — so the tests were fixed rather than the mutants.
bare word
rant[s], which put a record inside the`rants.jsonl`code span andduplicated a first timestamp into a second one. Both are now impossible: the anchor
is a citation match, and every timestamp an insertion names must be in the mapping —
an unmapped one aborts instead of writing a plausible-looking record. The guard's
citation word is a named group for the same reason (
group(0)is the wholematch, timestamp included).
tests/test_rant_citations.pyis 2619 / 16, so this PR adds exactly +20 testsand moves no existing test's count.
tests/test_journal_prompt.pyhas threeassertion strings updated because they pinned the citation spelling itself.
scripts/check-doc-count.py, the import check andpython -m emrg --helpare green.Not in this PR
evolution_prompt.md, 9 sites) — this iswhat The shipped evolution_prompt.md cites host-local rant timestamps no other instance can resolve #1252 measured, and it stays for the host. Routine evolution is forbidden to
edit that file (its own §Forbidden: it is a stable template, and the only exception
is a prompt-specific rant; The shipped evolution_prompt.md cites host-local rant timestamps no other instance can resolve #1252 is a reviewer's issue). Its sites are therefore
listed in the guard's
DEBTwith the reason spelled per entry, so the change isvisible, one edit away, and tested to shrink: a debt entry that no longer occurs
is itself a failure, which is why its date-less
+ 11:00:31is now printed by--measureinstead of being silently exempt.