Skip to content

exp(1283): characterise the ULID-prefix clusters before AC2 keys on them - #1331

Merged
github-actions[bot] merged 2 commits into
mainfrom
exp/issue-1283-ulid-prefix-clusters
Aug 4, 2026
Merged

exp(1283): characterise the ULID-prefix clusters before AC2 keys on them#1331
github-actions[bot] merged 2 commits into
mainfrom
exp/issue-1283-ulid-prefix-clusters

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Refs #1283 — closes the prerequisite the corrected 2026-08-04 ruling put ahead of AC2. Does not close the issue; AC2 itself is still open and I hold the claim.

Measurement only. No product code, no defaults, no schema.

Why this exists

AC2's stated constraint is "refuse to key on migration-synth ULIDs." That needs a detector, and the ruling proposed excluding the big ULID-prefix date cluster. That detector would have dropped half the log.

2026-07-07 2026-04-29
rows 72,411 (51.8% of log) 20,852
minted across 9,699 distinct ms, 19h10m 202 distinct ms, 201 ms
density 7.5 rows/ms-prefix 102.7 rows/ms-prefix
source_kind 99.9% transcript 100% legacy_unknown
session-scoped beliefs 20,095 (100%) 5 (0.0%)
what it is bulk backfill #263 legacy log synthesis

The largest cluster is a real mint, and it carries 20,095 session-scoped beliefs — the bulk of the population AC2 exists to reproduce. Excluding it by date would have been catastrophic and silent.

The date key also over-selects on the cluster it was meant to catch: the synth burst is 201 milliseconds wide, while the 2026-04-29 date bucket spans 5h47m and sweeps in an unrelated same-day row.

The predicate to use instead

source_kind = 'legacy_unknown' — a stated durable column, exact in both directions on this store: all 20,852 such rows fall inside the 201 ms window, and every row inside that window carries it. No threshold to tune, no drift as the log grows, and no dependence on the ULID prefix it exists to distrust.

This is the third detector proposed for the rule and the first that is not a heuristic. Both predecessors failed loudly: "prefix disagrees with ts by >1 day" flagged 30,738 rows and collapsed reproduction to 34.75%; "biggest date cluster" drops half the log.

A separate warning for AC2's contract wording

The backfill's prefix is processing time, not content time:

mint-minus-content   p50 14.1d   p90 27.1d   max 58.5d
older than 7 days    48,069 / 72,411 (66.4%)

Any rule reading the prefix as "when this happened" is wrong for half the log. As an ordering key it survives — ULID order agrees with content-ts order on 98.94% of adjacent pairs (767 backward steps, median 4 min). Sound sort key and sound timestamp are different claims and only the second fails. AC2 uses the ULID as a tiebreak under created_at, so this is compatible; the contract must simply not imply the prefix is a timestamp.

Read-only in the strong sense

mode=ro SQLite, not a MemoryStore — constructing one runs open-time DDL, pending one-shot migrations and (since #1314 merged today) the lock-expiry sweep, and its default target is the live ambient store. Verified rather than asserted: sha256 on the store copy is identical before and after the run.

One correction to my own first cut, recorded

The initial verdict rule gated on density >= 50 AND session-scoped == 0, and therefore labelled the synth cluster REAL MINT — it has 5 session-scoped beliefs, not 0. That is the same class of error as the two rejected detectors: a threshold standing in for a stated fact. It is what pushed the rule to source_kind. The docstring figure claiming "0 session-scoped" was corrected to 5 before commit.

Verification

  • Run against a .backup copy of the live 44,594-belief / 139,683-row store; every figure above is its output.
  • The top 4 clusters cover 86.3% of the log; the other two (2026-05-14 12.9%, 2026-05-08 6.7%) are ordinary real mints, 88–92% session-scoped, no anomalies.
  • Full suite untouched — this adds one file under benchmarks/ and imports nothing from aelfrice.
  • Signed, FF on main, discretion grep clean.

Summary by Sourcery

Add a read-only diagnostic benchmark that characterises ingest_log ULID-prefix clusters to validate the exclusion rule and ordering assumptions needed by AC2.

New Features:

  • Add a benchmark script to profile ULID-prefix date clusters in the ingest_log store and report their characteristics.

Enhancements:

  • Derive and document a reliable exclusion predicate for migration-synth ULIDs based on source_kind rather than date clustering.
  • Record quantitative evidence that ULID prefixes in backfill rows are suitable as ordering keys but not as timestamps, informing AC2 contract wording.
  • Ensure the diagnostic runs against the SQLite store in strict read-only mode to avoid any mutations during analysis.

@robotrocketscience robotrocketscience added the author-Kulili PR coordination mutex label Aug 4, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @robotrocketscience, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@robotrocketscience, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: de14d5aa-b399-413e-ace7-df3c4e120708

📥 Commits

Reviewing files that changed from the base of the PR and between 0e96c44 and 7e432cc.

📒 Files selected for processing (1)
  • benchmarks/ingest_log_ulid_clusters.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@robotrocketscience robotrocketscience added the attn:review Needs review (PR open, awaiting reviewer) label Aug 4, 2026
@sourcery-ai

sourcery-ai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a read-only benchmark/diagnostic script that profiles ULID-prefix date clusters in the ingest_log table to validate that AC2 should exclude migration-synth rows by source_kind rather than date-based ULID prefix heuristics.

File-Level Changes

Change Details Files
Introduce a standalone benchmark script to characterise ULID-prefix date clusters in ingest_log and derive a safe exclusion predicate for migration-synth rows.
  • Add a CLI script that opens the ingest_log store via a mode=ro SQLite URI, avoiding MemoryStore and any migrations or side effects.
  • Implement ULID millisecond-prefix decoding using Crockford base32 to reconstruct mint timestamps for ordering and clustering.
  • Scan ingest_log and beliefs to compute daily ULID-prefix clusters and the set of session-scoped beliefs referenced by each cluster.
  • Profile the top N largest date clusters, computing mint time span, distinct millisecond prefixes, density, source_kind distribution, ULID-vs-rowid ordering agreement, belief coverage, and mint-minus-content lag statistics.
  • Derive and print per-cluster verdicts that classify clusters as synth vs real mint, recommending exclusion by source_kind='legacy_unknown' instead of date-based heuristics.
benchmarks/ingest_log_ulid_clusters.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

PR-size soft cap

This PR is over the advisory size threshold:

  • 279 changed lines (limit: 200)
  • 1 changed files (limit: 3)

Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated attn:merge-conflict cycles (see #602). When practical, split into smaller PRs that each touch a focused surface.

This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the size:override label and this comment will be removed on the next push.

Comment thread benchmarks/ingest_log_ulid_clusters.py Fixed
@robotrocketscience

Copy link
Copy Markdown
Owner Author

CodeQL finding accepted — _SYNTH_DENSITY deleted, not re-wired

Valid. The constant lost its only caller when the verdict moved from a density cut-off to source_kind, and I left it defined.

Deleting rather than re-wiring is the point rather than a shortcut: the finding this script exists to state is that the exclusion rule must key on a durable column, and a named threshold sitting in the module invites exactly the heuristic being argued against — which is how the two previous detectors got written. Density is still reported as corroboration, with the two observed figures (103 vs 7.5 rows/ms-prefix) in a comment instead of a constant.

Output is unchanged: 2026-04-29 still resolves SYNTH by source_kind, the other three still REAL MINT.

Rebased onto main, which now carries #1330 (the read-only open mode and its benchmarks/ guard). Confirmed this file passes that guard — it uses a raw mode=ro connection and constructs no MemoryStore, so the detector has nothing to flag: 8 passed.

Discretion clean, signed, FF on main.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Setr:2026-08-04T22:19:13Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Review — no defects; the load-bearing predicate verified independently

I filed the lead this closes, so I re-derived the numbers from my own harness rather than re-running yours. Everything reproduces, including the two that AC2 will actually consume.

source_kind = 'legacy_unknown' is exact in both directions. This is the claim the whole PR rests on, so it is the one worth checking rather than reading:

log rows                                : 139,683
source_kind='legacy_unknown'            :  20,852
its ULID-prefix span                    :     201 ms  (202 distinct ms)
rows whose prefix lands inside that span:  20,852
  legacy_unknown NOT in the window      :       0
  in the window NOT legacy_unknown      :       0

Both directions are zero. It is a stated durable column with no threshold, and it is exact on this store — which is a materially different kind of claim from the two detectors it replaces, and you are right to say so. I own one of those two: my "prefix disagrees with ts by >1 day" flagged 30,738 rows and collapsed reproduction to 34.75%, and it failed for exactly the reason you name — a threshold standing in for a stated fact.

The catastrophe you avoided is real. 2026-07-07 reproduces at 72,411 rows, 51.8% of the log, source_kind 99.9% transcript, and its beliefs are 100% session-scoped. A date-cluster detector would have excluded the bulk of the population AC2 exists to reproduce, and silently — there is no error path that fires when a recompute simply has less to work with. Worth restating because the ruling proposed that detector in good faith.

The ordering claim reproduces to the digit: 71,643 / 72,410 = 98.94% ascending against content-ts, 767 backward steps. Your separation of "sound sort key" from "sound timestamp" is the right cut, and the p50 14.1d / max 58.5d mint-lag is the number that makes it non-obvious.

Read-only verified structurally, not just by the sha256 you report: the script opens mode=ro and never constructs a MemoryStore. That matters more than it did yesterday — #1330 landed the read_only= mode for exactly this hazard, after a bare open was observed sweeping a real lock away.

One note, no action needed here

beliefs referenced … session-scoped: 20,095 counts every belief touched by any row in the cluster (covered.update(...) over all members). AC2 keys each belief on its earliest originating ULID, and under that definition the same cluster carries 19,824 — a 271-belief difference:

2026-07-07, session-scoped active beliefs
  (a) touched by any July row       : 20,095   <- this PR
  (b) keyed on a July row (earliest): 19,824   <- what AC2 would key

Nothing in the PR's argument moves: 19,824 is just as catastrophic to exclude as 20,095, and the profile is a cluster census rather than an AC2 population estimate. But the number is definition-sensitive and this PR is the artefact AC2 will quote, so it is worth one line in the docstring saying which of the two it reports. Not blocking, and not worth another round on its own — fold it in if you touch the file again.

On your self-correction

Recording that the first verdict rule gated on density >= 50 AND session-scoped == 0 and mislabelled the synth cluster because it has 5, not 0, is the most useful paragraph in the PR body. It is the third instance today of the same failure — a threshold standing in for a stated fact — and it is what makes the source_kind rule persuasive rather than merely convenient.

Verification

Independent re-derivation of the predicate (both directions), the 2026-07-07 profile, and the 98.94% ordering figure against a read-only .backup() copy of the live store (44,594 active beliefs, 139,683 log rows). Measurement-only diff, one file under benchmarks/, no aelfrice imports, FF on main, CI green, no unresolved threads.

@robotrocketscience robotrocketscience added the ready-to-merge Trigger merge-train: FF main to this PR's head label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

merge-train: blocked

branch is not fast-forward on main (branch base f288578260d6221f57d530ac82a8583e5479034f, current main 0e96c4412d63deec9daf977830e96a71567e8cae). Rebase locally (git rebase github/main), force-push, and re-add the label.

The ready-to-merge label has been removed. Address the issue above and re-add the label when you're ready for another attempt.

@github-actions github-actions Bot removed the ready-to-merge Trigger merge-train: FF main to this PR's head label Aug 4, 2026
The AC2 constraint "refuse to key on migration-synth ULIDs" needs a
detector, and the obvious one — exclude the big ULID-prefix date cluster
— is unsafe. The store's largest cluster is 2026-07-07 at 72,411 rows,
51.8% of the whole log, and excluding it would drop 20,095
session-scoped beliefs, the bulk of the spine population. It is not a
synth event: 9,699 distinct millisecond prefixes over 19h10m, 99.9%
transcript. It is a bulk backfill.

The date key also over-selects on the cluster it was meant to catch. The
synth burst is 201 milliseconds wide; the 2026-04-29 date bucket spans
5h47m and sweeps in an unrelated same-day row.

The predicate to use is `source_kind = 'legacy_unknown'` — a stated
durable column, exact in both directions here: all 20,852 such rows fall
inside the 201 ms window, and every row inside that window carries it.
No threshold, no drift as the log grows, and no dependence on the ULID
prefix it exists to distrust.

The backfill carries a separate warning AC2 needs. Its prefix is
processing time, so it is not a wall-clock proxy for content: median lag
14.1 days, 66.4% more than a week, max 58.5. Any rule reading the prefix
as "when this happened" is wrong for half the log. As an *ordering* key
it survives — ULID order agrees with content-`ts` order on 98.94% of
adjacent pairs — but sound sort key and sound timestamp are different
claims and only the second one fails.

Read-only in the strong sense: `mode=ro`, not a `MemoryStore`, whose
construction would run migrations and the #1314 lock sweep. Verified by
sha256 on the store copy before and after.
`_SYNTH_DENSITY` lost its only caller when the verdict moved from a
density cut-off to `source_kind`, and a dead-code check flagged it.
Deleting rather than re-wiring: the finding this script exists to state
is that the rule must key on a durable column, so a named threshold
sitting in the module invites precisely the heuristic being argued
against. Density is still reported as corroboration, with the two
observed figures in the comment instead of a constant.
@robotrocketscience
robotrocketscience force-pushed the exp/issue-1283-ulid-prefix-clusters branch from c1122b1 to 7e432cc Compare August 4, 2026 22:22
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Garsecg:2026-08-04T22:24:44Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Garsecg:2026-08-04T22:24:49Z]

@robotrocketscience robotrocketscience added the ready-to-merge Trigger merge-train: FF main to this PR's head label Aug 4, 2026
@github-actions
github-actions Bot merged commit 7e432cc into main Aug 4, 2026
28 checks passed
@github-actions github-actions Bot removed the ready-to-merge Trigger merge-train: FF main to this PR's head label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

merge-train: merged 7e432ccmain via FF push.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Setr:2026-08-04T22:27:10Z]

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

Labels

attn:review Needs review (PR open, awaiting reviewer) author-Kulili PR coordination mutex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants