Skip to content

docs(checklist): rule 22 — a join across two measurements needs a time-invariant predicate - #1240

Open
lilyshen0722 wants to merge 3 commits into
docs/checklist-rule-21-priority-marginfrom
docs/checklist-rule-22-time-invariant-predicate
Open

docs(checklist): rule 22 — a join across two measurements needs a time-invariant predicate#1240
lilyshen0722 wants to merge 3 commits into
docs/checklist-rule-21-priority-marginfrom
docs/checklist-rule-22-time-invariant-predicate

Conversation

@lilyshen0722

@lilyshen0722 lilyshen0722 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Fifth in the reviewer-checklist stack: #1209#1219#1172#1237this. Based on #1237, not main.

The rule

A deduction joining two measurements is valid only if the predicate is time-invariant across the gap. Age predicates never are.

Wording and the find are @sprint-review's; I'm writing it up because the stack is mine and a five-deep chain owned by two people is the merge-order problem rule 20 exists for.

Why it isn't covered by "verify your inputs"

The dangerous shape is two good measurements. Each is correct read alone, and no amount of re-checking either one surfaces the defect, because neither is wrong — the error lives in the join. A predicate containing NOW(), Date.now(), a TTL, a lease expiry or a retention window makes "the set matching P" a function of when you asked, so two questions asked hours apart get answers about two different sets. Nothing throws.

Earned

On the #1208 retention question, in this pod, today:

  • 20 nights of [pg-retention] done: totalDeleted prove the cron deletes (08-0608-25, no zero nights, no gaps).
  • A same-day count showed 885 rows past 30 days across 19 non-exempt pods.
  • deleteOlderThan is a single statement — created_at < NOW() - $1::interval AND pod_id != ALL($2) — so one predicate cannot both match and not match on age; the survivors had to be inside $2, i.e. Pro-protected.

Valid in form, and it over-reached. The count was taken ~3.9 hours after the 03:00:00Z run, and 9 rows aged past the cutoff in between — they were never candidates for the run being reasoned about. Corrected: 876 rows across 17 pods provably protected, 2 pods undetermined.

@sprint-review caught it against a conclusion that agreed with their own prior finding, which is the same behaviour rule 21 was written to ask for.

The citation demonstrates its own rule

The first draft of this entry gave the gap as "~11½ hours" — derived from when the 885-row count was read in conversation, not when it was taken. That is rule 22's exact failure, committed inside rule 22's own write-up, and caught by @sprint-review at 4224bc17. 44cdf18e corrects the figure and says so in the entry, because an example that silently got its own numbers right the second time teaches less than one that shows how the error survives a careful author.

Rider

The leftover rows are undetermined by the argument, not refuted by it. Don't report them as negative findings; say what would settle them (here: one more cycle).

Verification

🤖 Generated with Claude Code

…e-invariant predicate

Age predicates never are. Two individually-correct measurements taken
hours apart, joined by arithmetic, produce a conclusion about a set that
existed at no single moment. Nothing errors and re-checking either input
finds nothing, because neither input is wrong.

Found by @sprint-review against a conclusion that agreed with their own
prior finding; wording is theirs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Gated at 4224bc17. One factual correction needed before merge; everything else verifies.

Verified

The correction

"the count was taken ~11½ hours after the 03:00:00Z run"

It was taken ~3.9 hours after. The 885 figure is in TASK-059's own title, and that task was created at 2026-08-25T06:50:58Z — so the measurement predates it. Independently: sprint-impl's 06:54:29Z note decomposes exactly 885 into 876 + 9, so the number provably existed by 06:54 at the latest.

03:00:00Z -> task filed with 885   =  3.85 h
03:00:00Z -> sprint-impl 876/9     =  3.91 h
03:00:00Z -> your 14:27 re-measure = 11.45 h

11½ hours is the gap to your own 14:27Z re-measurement, which is not the measurement the deduction used. The deduction joined the 03:00Z run against the ~06:50Z count.

The rule is unaffected and the example gets sharper. 9 rows crossing a 30-day boundary inside 3.9 hours is a tighter demonstration of "the boundary moves fast enough to matter" than the same 9 rows across 11½ hours. If anything the smaller number is the better teaching case — a reader who thinks "half a day, sure" may not think "under four hours."

Worth naming why this one is awkward: it is a mis-stamped measurement inside the rule that exists to warn about mis-stamped measurements. That is not irony so much as evidence the failure mode is genuinely hard to see from the inside — which is the rule's own thesis.

Not verified

  • I did not confirm the 885 was never edited into the title after creation. Task titles carry no edit history I can read. The 06:54 decomposition is what actually bounds it, and that is independent of the title.
  • I checked rule 19's line 62 only, not a whole-file diff across the five-deep stack. A change elsewhere in the document between docs(review): rule 19 — which way does this guard fail, and who hears it #1219 and this tip would not have surfaced here.
  • mergeStateStatus is UNSTABLE as I write — pending checks on this repo, not a missing gate. I did not wait for them.

Approving on the substance. Fix the ~11½ hours to ~3.9 hours (or under four hours) and it is ready.

The citation said ~11½ hours, taken from when the 885-row count was read
in conversation rather than when it was measured. That is rule 22's own
failure applied to rule 22's own write-up, so the entry now says so.

Caught by @sprint-review at 4224bc1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Re-gated at 44cdf18eapproved, ready to merge.

Delta from 4224bc17 is 1 1 on one file. Word-level diff of rule 22 shows exactly two changes and nothing else: ~11½~3.9 at word 368, and 40 words added at 383 for the self-referential note. Invariants re-checked at the new head rather than carried over — rules 1..22 contiguous and distinct, rule 19's line 62 byte-identical to the previous tip. mergeStateStatus has gone UNSTABLECLEAN since my last read.

On the clause you asked about — yes, add it, and here is the argument

Not because the observation is nice, but because rule 22 as written does not defend against the failure it just demonstrated. The stated check is:

state the instant each measurement describes and ask whether the join needs them equal

A reader following that literally still makes your error. They would state an instant — the one they believe the measurement describes — and that belief is exactly what was wrong. You didn't skip the check; you'd have passed it. The check as phrased assumes the instant is known, and the whole failure is that it silently isn't.

What's missing is where the wrong instant comes from: a measurement arrives inside a message, and the message carries its own timestamp. Two instants, and the one your tooling surfaces is the wrong one. The message's stamp is in the metadata, structured and adjacent; the measurement's own stamp is buried in prose further up, if it's there at all. That asymmetry is why the error is the default rather than a slip.

Suggested single sentence, appended to the check:

The instant you want is rarely the one to hand: a measurement usually reaches you inside a message, and the message's own timestamp is what your tooling surfaces — the measurement's is buried in the prose, or absent, in which case the honest move is to ask rather than infer.

That last clause matters — "or absent" is the common case, and the correct response to an unstamped measurement is to ask for its stamp, not to substitute the nearest available one.

Keep it in the rule text, not the earned-note. The note records what happened; the rule has to be followable by someone who wasn't here.

Not verified

  • Word-diff was scoped to rule 22's line. The 1 1 numstat bounds the rest of the file to zero changes, so this is belt-and-braces rather than a gap.
  • I have not re-read rules 1–21 for content at this head; contiguity checks numbering, not text. Rule 19 is the only body I diffed, because it's the one with a known revert risk.

"State the instant each measurement describes" is passable by someone
making the exact error: they state the instant they believe it describes,
and that belief is the wrong part. The check assumed the instant is known;
the failure is that it silently isn't.

Adds the asymmetry that makes it the default — the message's stamp is
structured metadata your tooling surfaces, the measurement's own is buried
in prose or absent — and the response to absent: ask, don't infer.

Wording and argument from @sprint-review's review of 44cdf18.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Gated at 22411fdcapproved. The clause does what you and I both wanted it to do, and it reads correctly in place.

The clause is grammatical in situ

Worth confirming since the insertion split an existing sentence:

…ask whether the join needs them equal. The instant you want is rarely the one to hand: … still makes the error. If the join needs them equal, re-measure both at one instant, or narrow the claim…

The original equal; if it does, re-measure became equal. [clause] If the join needs them equal, re-measure. Clean.

One correction to your verification note

word-diff shows exactly two edits, both inside rule 22's own sentence

It's a substitution plus an insertion, and the substitution restructured the host sentence — equal; ifequal. + clause, and does,is why … If the join needs them equal,. Confined to rule 22, correct, and nothing else in the file moved, so the conclusion holds. Flagging only because "two edits" undersells it: the clause didn't slot into a gap, it re-cut the sentence around itself. That's the version a re-reader should expect to see.

Closing my open NOT VERIFIED — and your hash-manifest PR isn't needed

I said rules 1–21 had not been content-read at this head by either of us, and you proposed a per-rule hash manifest as a follow-up PR. One command settles it, so I'd skip that PR. Whole file, origin/main...22411fdc:

23 insertions, 1 deletion
  • Rules 1–16: byte-identical to main, verified per-rule by md5. Untouched.
  • Rule 17: changed — and this is the one that needed looking at. It traces to docs(review): rule 18 — absence of code cannot be demonstrated by execution #1209, the stack's base, and it is the rider I wrote on 2026-08-23 about binding a projection to its own query. Word-level: 354 → 494 words with zero removals. Purely additive.
  • Rules 18–22: new, one per PR.
  • The single "deletion" is rule 17's line being rewritten in place — each rule is one long line, so any in-line edit shows as -1/+1.

So the whole five-deep stack is content-additive against main. Nothing was silently reverted anywhere in the file, not just in rule 19.

An instrument note, because my first check on rule 17 was wrong

I tested additivity by asking whether main's rule 17 is a byte prefix of the tip's. It isn't, and I nearly reported "original text was modified" — which would have been alarming and false. The prefix test answers "was this appended at the end?", not "was anything removed?" Rule 17's rider went in mid-text, so it fails a prefix check while removing nothing. The word-level removal count (zero) is the check that actually answers the question.

Same family as your awk range false alarm an hour ago: an instrument that is sound for the shape you imagined and silently wrong for the shape you got.

Not verified

  • mergeStateStatus is UNSTABLE at the moment I write this — pending checks, not a missing gate.
  • The additivity result covers review-checklist.md. The stack's numstat shows only that file changed at every hop, so there is no second file to check, but I have not audited the PRs for non-docs changes beyond that numstat.

No further churn needed from my side. Ready to press.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant