backlog: file #1228 -- mail expiry is silent in both directions, and candidate (a) is measured out - #339
Merged
Merged
Conversation
…d the symptom Session mail past its TTL is swept to expired/ and NOTHING REPORTS IT AT EITHER END. The recipient is never told a message existed; the sender is never told its message went unread; and the send call already returned success. Every observable on both sides says the message was delivered. The item exists because the obvious fix was the wrong one. Raising the TTL 720 -> 4320 (12h -> 72h) is correct and stands, but it lowers the FREQUENCY of the failure and does not touch the SILENCE -- and a rarer invisible failure is harder to attribute, not easier. Records the reconciliation that makes this TTL legitimate at all, because the surrounding design rejects TTLs outright: state versus message. No expiry on HELD STATE, because expiry re-opens the race it exists to prevent; mandatory expiry on MESSAGES, because a stale instruction acted on is worse than one refused. So the TTL is right to exist and only its silence is the defect. Scope is to make expiry OBSERVABLE, not longer, cheapest candidate first: the drain already prints an expired count, so verify a message swept for age actually reaches that tally rather than being removed on a path that never increments it -- a counter reading zero because nothing increments it is the same defect one level up. Then the sender-side signal, which is the half no recipient-side counter can supply. How to prove a fix is stated in both directions deliberately: a test that only checks the recipient half leaves the sender blind, which is the direction that made this invisible to begin with. Provenance is recorded in the item because it bears on the framing: the TTL raise was mine, and the observation that it treated the symptom came from the reviewer of the doc change. I had written "expiry is silent in both directions" in that commit message and still concluded raising the duration was the fix. Number allocated atomically as #1228 from this worktree. Gate green at 494 items, each declaring exactly one status.
… the item narrows
The scope proposed checking, cheapest first, whether a message swept for AGE
reaches the drain's expired tally or is removed on a path that never increments
it. It reaches the tally. Driven twice against the live drain, each run with a
NEGATIVE CONTROL so the counter had to discriminate rather than merely be
non-zero: one message written already past its expiry (-TtlMinutes -1, so no
timing race) and one fresh message that had to be shown instead.
run 1 1 shown, 1 expired expired/ on disk 0 -> 1
run 2 1 shown, 1 expired expired/ on disk 1 -> 2
The counter increments only for the swept message, the fresh control is shown
and not swept, and the file lands in expired/ on disk. mail-drain.ps1:758
increments on the age path and :900 renders it. So the recipient-side half was
already built and already correct, and the guess that it might not be was mine.
What survives is narrower and better located: the SENDER is told nothing ever,
which no recipient-side counter can close; the recipient is told HOW MANY
expired but never WHICH, so the count cannot be turned into "whose message did I
lose" without reading expired/ by hand; and a recipient that never runs a drain
is told nothing either, which is the case the TTL raise was about.
Recorded because the process is the point: the code was read first and predicted
this outcome, and it was measured anyway, with a control that could have returned
the opposite. A reading that agrees with the code is not a measurement, and a
counter reading non-zero is not proof it discriminates -- the negative control is
what separates "the counter works" from "the counter counts everything".
…#1228's header
The hunk was a keep-both-sides trap. OUR side carried #1228's HEADER ALONE;
THEIR side carried #1226 and #1227 complete; and #1228's BODY sat BELOW the
>>>>>>> marker, owned by neither side. A mechanical keep-both would have emitted
## 1228. header
## 1226. complete
## 1227. complete
> banner <- #1228's body, now trailing #1227
with every line of both sides present, zero markers left, and the ledger WRONG:
#1228 orphaned from its own banner and declaring no status, breaking the
one-banner invariant, while #1227 gained foreign trailing prose. Nothing about
that result looks unresolved.
Resolved theirs-first so #1228's header re-adjoins the body that already follows
the hunk. Resolution was written by hand against the conflict rather than copied,
then cross-checked: byte-identical to the coordinator's independent resolution
after folding line endings, which is two resolutions agreeing rather than one
trusted. The whole-file diff before folding was the CRLF signature, not a
difference.
Verified with the real parser rather than by reading, since the banner alphabet
is machine-parsed: 496 items, each declaring exactly one status, docs/BACKLOG.md
260 and the archive 236, zero conflict markers.
Committed from this worktree because the ledger gate correctly refused the
coordinator's commit -- #1228's allocation record names this worktree, so the
gate was refusing the WORKTREE and not the item. No bypass was used by either of
us.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Files #1228 -- session-to-session mail past its TTL is swept to
expired/rather than shown, and nothing reports it at either end. The recipient is never told a message existed; the sender is never told its message went unread. The send call already returned success, so every observable on both sides says the message was delivered.Filed because the TTL raise in #337 treated the symptom: raising 720 to 4320 minutes lowers the frequency of silent loss and does not touch the silence.
The item narrowed on a measurement, and the measurement disproved its own first candidate
As filed, #1228 proposed checking -- cheapest candidate first -- whether a message swept for AGE actually reaches the drain's
expiredtally, or is removed on a path that never increments it. A counter reading zero because nothing increments it is the same defect one level up.It reaches the tally. Driven twice against the live drain, each run carrying a negative control so the counter had to discriminate rather than merely be non-zero -- one message written already past its expiry (
-TtlMinutes -1, no timing race) and one fresh message that had to be shown instead:mail-drain.ps1increments on the age path and renders it. So the recipient-side half was already built and already correct.The item is therefore amended rather than closed, and the surviving defect is smaller and exactly located: the sender is told nothing, ever, and no recipient-side counter can close that. Secondarily, the recipient learns how many expired but never which, so "1 expired" cannot become "whose message did I lose" without reading
expired/by hand.Merge note
This branch collided with
mainindocs/BACKLOG.mdonce #338 landed #1226 and #1227. The conflict was not resolvable by keeping both sides: the hunk held only #1228's header on this side and the two complete items on main's, with #1228's body below the>>>>>>>marker. Keeping both in file order would have left every line present, zero conflict markers, #1228 orphaned from its own banner and declaring no status, and #1227 carrying foreign trailing prose -- a broken ledger that looks resolved.Resolved by structure: main's two completed items first, then #1228's header so it adjoins the body it owns. Resolved independently by two sessions and compared byte-for-byte after folding line endings (the raw diff reported every line changed from line 1, which is the CRLF signature rather than a difference).
Verified with the parser rather than by reading, since the banner alphabet is machine-parsed:
496 backlog items, each declaring exactly one status-- 260 live, 236 archive, zero in both.Authored by the backlog-clearing session; merged in that worktree under its own ledger allocation, pushed and opened by the coordinator.