Skip to content

fix(spine): report both directions of the divergence and correct its denominator (#1356) - #1378

Merged
github-actions[bot] merged 9 commits into
mainfrom
fix/issue-1356-spine-divergence-meter
Aug 6, 2026
Merged

fix(spine): report both directions of the divergence and correct its denominator (#1356)#1378
github-actions[bot] merged 9 commits into
mainfrom
fix/issue-1356-spine-divergence-meter

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Closes #1356.

aelf spine verify shipped in #1336 without either half of the ratified
constraint (2) on #1283 AC4. All three gaps closed.

1. The meter was one-directional

It iterated shipped - recomputed only, so links the recompute produces that
the shipped spine does not have were never named — 2,102 of them on the
development store. A meter that sees one direction cannot distinguish "the
recompute missed links" from "the recompute invented links", and those have
opposite fixes. Now reported as n_recomputed_only.

2. The fan-in surplus sat in the denominator

546 successors carry more than one predecessor edge. A chain gives each
successor exactly one, so on a fan-in-2 successor the recompute can only ever
reproduce one of the two shipped edges — the other is a guaranteed miss no key
can avoid. Leaving them in charges the candidate key for a writer defect it
cannot express, which is exactly the single depressed percentage the constraint
forbade.

Both of a fan-in successor's edges leave the denominator, not just the
missed one. Dropping only the miss moves numerator and denominator by different
amounts and inflates the share rather than correcting it. The test separates
those two readings explicitly — a single-session fixture cannot, because both
give 1.0; it takes a second session contributing an eligible miss, after which
"drop both" gives 1/2 and "drop only the miss" gives 2/3.

The published number moves

value denominator
before 93.68% 39,335 / 41,984 (all shipped)
after 94.86% 38,789 / 40,892 (fan-in-1 eligible)

This is a denominator correction, not a movement in fidelity. Do not compare
the two.
Both figures now carry that statement, in the CHANGELOG and in the
write-log memo.

The direction is data-dependent, not fixed — the share rises when the
excluded edges were reproducing worse than the overall rate and falls when
better. Here they reproduced at 50% against an overall 93.7%, so it rises; on
this module's own CLI fixture the same correction moves 40% → 33.33%. I had
written "the correction raises it" before checking the fixture, and that would
have shipped as a false general claim.

Per the project rule that published numbers ship their script,
benchmarks/spine_fan_in_baseline.py re-derives every figure and writes
benchmarks/spine_fan_in_baseline.json. It copies the store before opening it,
because MemoryStore.__init__ runs migrations and a lifecycle sweep — opening a
live store is a write.

3. "Non-increasing" existed only as prose

It appeared in spine_recompute's docstring and in printed output, with no
baseline committed anywhere
, so nothing could assert it.
fan_in_regressed_against now compares against the committed baseline.

Equal is not a regression — the surplus is a standing writer defect this issue
measures rather than fixes, so holding steady is the expected state and only
growth is signal. Asserted in all three arms (grew / held / shrank), because the
two failure modes are opposite: >= flags the steady state, and a percentage
tolerance lets real growth through.

CI cannot re-derive the baseline — the store it was taken on is not a shipped
fixture — so the committed JSON is instead checked for internal consistency: its
recorded share must follow from its own numerator and denominator, its eligible
set must be a strict subset, and it must not equal the pre-correction figure. A
hand-edited or stale baseline fails that.

Verification

  • Full suite green: 7244 passed, 70 skipped, 71 xfailed.
  • Every new assertion mutation-checked; four mutations run, all caught:
    • eligible set keeps reproduced fan-in edges (drop-only-the-miss) → caught
    • n_recomputed_only hardcoded to 0 → caught
    • non-increasing check using >= → caught by the held arm alone
    • reproduced_share reverted to the uncorrected denominator → caught
  • The three figures were re-derived against the live store before being written
    down, and the arithmetic is self-consistent: the 546 fan-in-2 successors
    contribute 1,092 shipped edges, of which exactly 546 reproduce — one per
    successor, as a chain requires — which is precisely missing_fan_in.
  • Two existing CLI-format tests updated: the reproduced line now names the
    eligible denominator, which is a deliberate contract change rather than a
    loosened assertion.
  • Discretion grep on added lines: clean. Both commits signed.

Not in scope

Closing the gap to the 98.70% structural ceiling still needs the writer to order
on the same durable key, which remains unfunded. This is a meter, not a rebuild.

Summary by Sourcery

Adjust spine divergence reporting to account for both recomputed-only edges and fan-in-related writer defects, and publish a corrected, script-derivable reproduction share baseline.

New Features:

  • Expose recomputed-only edges and fan-in > 1 successor counts in the spine divergence report and CLI output.
  • Provide a benchmark script and JSON baseline to re-derive and lock in the corrected spine reproduction share and fan-in surplus figures.

Enhancements:

  • Change the reproduced_share metric to use an eligible fan-in-1-only denominator so writer fan-in defects are excluded from the key’s fidelity measurement.
  • Extend SpineDivergence with additional counters for recomputed-only edges and eligible shipped/reproduced edges, and surface these in CLI diagnostics with clearer messaging.
  • Document the corrected denominator, new baseline figures, and non-comparability with the previous 93.68% metric in the changelog and design docs.

Tests:

  • Add targeted tests to ensure recomputed-only edges are counted, fan-in successors are fully excluded from the denominator, fan-in-only defects do not reduce the share, and the new regression guard and baseline consistency checks behave as expected.

@robotrocketscience robotrocketscience added the author-idnn PR authored by session idnn label Aug 5, 2026
@sourcery-ai

sourcery-ai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Reviewer's Guide

Extends the spine divergence meter to report both directions of divergence and corrects the reproduced_share denominator by excluding fan-in>1 successors, adds a regression guard and committed baseline for the fan-in surplus, updates CLI/text output and docs accordingly, and introduces a benchmark script plus tests to lock in the new behavior and published figures.

Flow diagram for corrected spine divergence and denominator

flowchart LR
    A[shipped TEMPORAL_NEXT edges] --> C[compute fan_in per successor]
    B[recomputed TEMPORAL_NEXT edges] --> D[set operations]

    C --> E{fan_in == 1?}
    E -->|yes| F[eligible = shipped edges with fan_in 1]
    E -->|no| G[fan_in>1 successors counted
n_fan_in_successors]

    F --> H[n_eligible_shipped = |eligible|]
    F --> I[n_eligible_reproduced = |eligible ∩ recomputed|]

    D --> J[n_recomputed_only = |recomputed - shipped|]

    H --> K[reproduced_share
= n_eligible_reproduced /
  n_eligible_shipped]
    I --> K

    style G stroke-dasharray: 3 3
    style J stroke-dasharray: 3 3
Loading

File-Level Changes

Change Details Files
Spine divergence report now tracks recomputed-only edges and uses a fan-in-1-only eligible set to compute reproduced_share, with explicit fan-in accounting and regression logic.
  • Extended SpineDivergence with n_recomputed_only, n_fan_in_successors, n_eligible_shipped, and n_eligible_reproduced fields with an updated reproduced_share property that uses the eligible counts and treats zero-eligible as 1.0.
  • In spine_divergence, computed a fan-in-aware eligible set that entirely drops edges whose successors have fan-in>1, counts fan-in successors, and populates the new divergence fields including recomputed-only edges.
  • Introduced fan_in_regressed_against(observed, baseline) to enforce a strictly non-increasing fan-in successor count, treating equality as non-regression.
src/aelfrice/spine_recompute.py
CLI spine-verify output and tests now expose both divergence directions, the corrected denominator, and fan-in diagnostics.
  • Updated _cmd_spine_verify output to print a recomputed-only count, label reproduced_share as a percentage of the fan-in-1 eligible denominator, and print the count of fan-in>1 successors with explanatory text.
  • Adjusted existing CLI-format tests to expect the new reproduced line format and added assertions for recomputed-only counts and fan-in successor lines.
  • Added focused tests that validate recomputed-only counting, fan-in-based eligibility, reproduced_share behavior under different fan-in scenarios, regression detection via fan_in_regressed_against, and internal consistency of the committed baseline JSON.
src/aelfrice/cli.py
tests/test_spine_recompute_1283.py
Committed a fan-in baseline and benchmark script that re-derives the corrected share and enforces non-increasing fan-in surplus, plus documentation and changelog updates tying the figures to their provenance.
  • Added benchmarks/spine_fan_in_baseline.py to copy a store, run spine_divergence, emit all divergence figures, compare observed fan-in successors to the committed baseline, and optionally rewrite the baseline JSON.
  • Committed benchmarks/spine_fan_in_baseline.json containing the figures used as the fan-in baseline and the corrected reproduced_share, and added a test to ensure those figures are internally consistent and distinct from the pre-correction denominator.
  • Updated CHANGELOG and write-log-as-truth design doc to describe the bidirectional meter, denominator correction, new figures (93.68%→94.86%), and the non-comparability of pre- and post-correction percentages, referencing the benchmark script and baseline JSON.
benchmarks/spine_fan_in_baseline.py
benchmarks/spine_fan_in_baseline.json
CHANGELOG/v4.md
docs/design/write-log-as-truth.md

Assessment against linked issues

Issue Objective Addressed Explanation
#1356 Add a recomputed-only counter and bucket so both directions of the divergence (shipped-only vs recomputed-only) are reported by aelf spine verify.
#1356 Exclude successors with fan-in > 1 from the reproduced_share denominator, and ensure the meter reports the corrected denominator semantics.
#1356 Commit a baseline fan-in (>1 successor) count and add a test asserting that this count is non-increasing over time.

Possibly linked issues


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

@coderabbitai

coderabbitai Bot commented Aug 5, 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: 41 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: be464d0a-311a-4a65-a4ae-50d83c1e241d

📥 Commits

Reviewing files that changed from the base of the PR and between ec25ee6 and 7c55770.

📒 Files selected for processing (7)
  • CHANGELOG/v4.md
  • benchmarks/spine_fan_in_baseline.json
  • benchmarks/spine_fan_in_baseline.py
  • docs/design/write-log-as-truth.md
  • src/aelfrice/cli.py
  • src/aelfrice/spine_recompute.py
  • tests/test_spine_recompute_1283.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 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

PR-size soft cap

This PR is over the advisory size threshold:

  • 551 changed lines (limit: 200)
  • 7 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.

@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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="benchmarks/spine_fan_in_baseline.py" line_range="101-105" />
<code_context>
+        print(f"{k:<26} {v}")
+
+    baseline = load_baseline()
+    observed = measured["n_fan_in_successors"]
+    recorded = baseline["figures"]["n_fan_in_successors"]
+    print()
+    print(f"fan-in successors: observed {observed} vs baseline {recorded}")
+    if observed > recorded:
+        print("REGRESSED — the fan-in surplus grew; the writer defect widened.")
+    else:
</code_context>
<issue_to_address>
**suggestion:** Reuse `fan_in_regressed_against` instead of reimplementing the regression check

This regression check duplicates the logic in `fan_in_regressed_against` in `spine_recompute.py`. Please call that helper instead (or extract it to a shared module if needed) so the regression definition remains centralized and consistent if we later change the rule (e.g., equality vs. tolerance).

Suggested implementation:

```python
    baseline = load_baseline()
    observed = measured["n_fan_in_successors"]
    recorded = baseline["figures"]["n_fan_in_successors"]
    print()
    print(f"fan-in successors: observed {observed} vs baseline {recorded}")
    if fan_in_regressed_against(observed, recorded):
        print("REGRESSED — the fan-in surplus grew; the writer defect widened.")
    else:
        print("OK — non-increasing.")

```

To fully implement the suggestion, also:
1. Import the shared helper from `spine_recompute.py`, e.g.:
   - If `benchmarks` is a package: `from .spine_recompute import fan_in_regressed_against`
   - Otherwise (module-level script): `from spine_recompute import fan_in_regressed_against`
2. Ensure the signature of `fan_in_regressed_against` matches this usage (likely `fan_in_regressed_against(observed, recorded)`); if it instead expects a structure (e.g. full `measured` and `baseline` dicts), adjust the call accordingly so the regression criterion is centralized in that helper.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread benchmarks/spine_fan_in_baseline.py Outdated
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Garsecg:2026-08-06T00:11:56Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Gylf:2026-08-06T00:20:03Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Gylf:2026-08-06T00:20:07Z]

@robotrocketscience
robotrocketscience force-pushed the fix/issue-1356-spine-divergence-meter branch from 7b97720 to f52733a Compare August 6, 2026 00:37
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Review — all three gaps are real fixes, but the meter's own headline line does not divide, and the third gap is enforced by nothing

The diagnosis is right on all three counts and the hard part is done well. Two things in particular: refusing to drop only the missed edge of a fan-in successor is the correct reading and the arithmetic argument for it is sound; and catching your own "the correction raises it" claim against the CLI fixture before shipping it, then keeping the data-dependence in the docstring, is the discipline this repo keeps asking for.

I pushed four commits for what I could fix correctly. Four findings need you.

Everything below was reproduced against the committed baseline JSON or run locally. Where a claim rested on a mutation I cleared __pycache__ first — several of these edits are the same length as the original, and a same-second same-size rewrite is served from a stale .pyc, which makes the run report the opposite of the truth.


1. aelf spine verify printed a ratio it does not print — fixed in c75e2c9b

The share is over the fan-in-1 eligible subset, so its numerator is n_eligible_reproduced. The line printed n_reproduced, the count over all shipped edges, beside that share and its eligible denominator:

reproduced            : 39,335 (94.86% of the 40,892 fan-in-1 eligible)

39,335 / 40,892 = 96.19%. The actual numerator of 94.86% is 38,789, and it appeared nowhere in the output — so the one surface an operator runs could not re-derive the number this PR publishes. That is the opposite of what SpineDivergence's docstring says the two new fields are for: "carried as fields rather than recomputed at the property, so the subtraction is inspectable in the report rather than implied."

The existing test pinned the slip rather than catching it:

# 4 of 6 fan-in-1 eligible, not 4 of 10 (#1356). ...
assert "reproduced            : 4 (33.33% of the 6 fan-in-1 eligible)\n" in text

4/6 is 66.67%. The comment states the intended contract; the string states what the code does; they disagree in the same assertion. The fixture's eligible numerator is 2 — of its four reproduced edges only b<-a and z<-y have a fan-in-1 successor.

Both counts are worth having, so both are printed against the denominator each belongs to. Mutation-verified: putting n_reproduced back turns the test red.

2. The before/after pair spans two store snapshots — fixed in the docs commit

39,335 / 41,984 = 93.6905%, i.e. 93.69%, not the 93.68% the same sentence attaches to those counts. The 93.68% is #1336's measurement, 39,280 / 41,929 — a snapshot 55 shipped edges smaller — which the unchanged #1283 entry three lines below still reports as such.

So the pair presented as a pure denominator correction was measured across two store states, and ~0.01pp of the 1.18pp move is snapshot drift rather than the correction. spine_recompute.py:158 already says 93.69%, so the branch shipped both answers.

I have paired the corrected figure with this store's own before-figure and named the #1336 number separately, in the CHANGELOG, the design memo, the script docstring and the baseline JSON.

Related, also fixed: the memo's "98.70% structural ceiling" sentence was left on the old denominator. That ceiling is defined by the fan-in misses this correction removes, so as written the paragraph forbids the comparison in one sentence and invites it three sentences later.

3. The third gap is not closed — fan_in_regressed_against had no caller

This is the finding I'd most want you to look at, because the PR's framing of it is exactly inverted.

The stated defect was that "non-increasing" existed only as prose with nothing able to assert it. As merged, fan_in_regressed_against has zero call sites in shipped code. aelf spine verify never reads the baseline JSON. The comparison that actually ran was an untested inline observed > recorded inside the benchmark script, while the tested predicate sat unused and unexported from __all__ — and the equal case is the entire content of the rule, so it had two implementations and only one of them was covered.

The committed JSON also claimed the wrong thing about itself: "aelf spine verify compares against it on the operator's machine." It does not.

Fixed in 00500349 — the script calls the shipped predicate, it is exported, the JSON says what actually compares. But the constraint is still only enforced by a script somebody has to run. Whether that is enough is your call; if aelf spine verify should fail or warn against the baseline, that is a design decision I did not make for you.

4. measure() read past its own store — fixed in 00500349

shutil.copy(store_path, copy) takes the main database file alone. The store runs in WAL mode, so every commit since the last checkpoint lives in the uncopied -wal. Run against a live store — the documented and only intended use — the script measures a valid but stale snapshot, and a fan-in count short of the baseline prints OK — non-increasing off figures that were never the store's.

The copy existed only because opening a store runs migrations. read_only=True is the supported answer to that and is what _cmd_spine_verify already does at cli.py:6066, so both now read the same store the same way, through the same WAL.

Also in that commit: main() returned 0 unconditionally, including after printing REGRESSED, so nothing could gate on it. It now exits 1.


For you — four I did not fix

(a) reproduced_share returns 1.0 when the eligible set is empty. Vacuously fine on an empty store. But the same branch fires on a non-empty store where every successor carries fan-in > 1: the meter then reports 100% reproduced having compared nothing. This is the shape #1360/#1361 just went through — evidence disappearing read as agreement — and it is worth deciding deliberately rather than inheriting. The four new fields also default to 0, so a SpineDivergence built without them reports perfect fidelity while carrying n_shipped=41,984; only one construction site exists today, so this is latent, but the defaults make the most flattering answer the fallback.

(b) The exclusion is one-sided. A recomputed chain is injective on dst as well as on src, so two shipped edges sharing a predecessor are as guaranteed a miss as fan-in > 1 — yet they stay in the eligible denominator and land in missing_other. That makes cli.py's "(the only bucket a key disagreement moves)" falsifiable. I did not touch the denominator: the fan-in rule is ratified and a symmetric exclusion would move the published number again. But the absolute claim in the printed output should probably soften.

(c) The baseline consistency test is weaker than advertised. It constrains the share against its own numerator and denominator, leaving n_fan_in_successors — the single figure the non-increasing constraint reads — and all three miss buckets completely free. So "a hand-edited or stale baseline fails that" is not true: edit the fan-in count alone and nothing notices. Two identities that hold exactly on the committed figures would close it: n_shipped - n_eligible_shipped >= 2 * n_fan_in_successors, and the miss buckets summing against n_shipped - n_reproduced.

(d) cli.py:6095's "this exceeds the successor count only where fan-in > 2" is false at fan-in 2 — a fan-in-2 successor contributes two fan-in misses when the recompute assigns it a predecessor that is neither of its shipped ones.

Minor: --write replaces baseline["figures"] but leaves the top-level measured_at alone, so a re-derived baseline carries the previous measurement's date. And --write will happily baseline a regression upward — arguably it should refuse without an explicit flag.


Pushed

commit
c75e2c9b print the eligible numerator the share is actually over; fix the assertion and the comment that contradicted it
00500349 read the store read-only through its WAL instead of copying the main file; call the shipped fan-in predicate and export it; exit 1 on regression
f52733a6 pair 94.86% with this store's own 93.69%, name the #1336 figure separately, scope the 98.70% ceiling to its denominator

Full suite green (7244 passed); discretion grep on added lines clean. Also rebased onto current main — it had fallen behind while this was open — so the branch is FF and merge-ready.

Unrelated, found while reading this

triple_extractor is a second TEMPORAL_NEXT writer — 4 of its 25 patterns (follows, comes after, is after, succeeds) mint it directly. Those rows are in shipped and can never be in recomputed, so they are inside this PR's eligible denominator, charging the key for edges a different writer produced — the same shape as the fan-in defect one layer down. I have not adjusted for it here because I cannot separate them without a writer column. Filed as #1379, which also covers the sharper consequence: aelf spine clear deletes them by type and the backfill rebuilds them reversed, with the row count unchanged.

@robotrocketscience robotrocketscience added attn:unblock Needs answer from another session and removed attn:review Needs review (PR open, awaiting reviewer) labels Aug 6, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Garsecg:2026-08-06T00:37:40Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Kulili:2026-08-06T01:07:34Z]

…denominator

Three gaps against the ratified constraint (2) on #1283 AC4.

The meter iterated `shipped - recomputed` only, so links the recompute
produces that the shipped spine lacks were never named — 2,102 of them
on the development store. A one-directional meter renders "missed links"
and "invented links" identically, and those have opposite fixes.

The fan-in surplus sat in the share denominator. A chain gives each
successor exactly one predecessor, so on a fan-in-2 successor the
recompute can only ever reproduce one of the two shipped edges; leaving
them in charges the candidate key for a writer defect it cannot express.
Both edges are now excluded, not just the missed one — dropping only the
miss moves numerator and denominator by different amounts and inflates
the share instead of correcting it. The test separates those two
readings; a single-session fixture cannot, since both give 1.0.

"Non-increasing" existed only as prose with no baseline committed
anywhere, so nothing could assert it. `fan_in_regressed_against` plus a
committed baseline and its re-derivation script close that. Equal is not
a regression: the surplus is a standing writer defect this issue
measures rather than fixes.

Refs #1356.
reproduced_share was published as 93.68% against a denominator that
included the fan-in surplus. Under the corrected fan-in-1 eligible
denominator the same store reports 94.86%. Both figures now carry the
statement that they are not comparable, and the successor ships with the
script that re-derives it, per the project rule on published numbers.

States the direction as data-dependent rather than fixed: the share
rises when the excluded edges were reproducing worse than the overall
rate and falls when better. It rises here and falls on the module's own
CLI fixture, so "the correction raises it" would have been wrong as a
general claim.

Refs #1356.
…y over

`reproduced_share` is over the fan-in-1 eligible subset, so its numerator is
`n_eligible_reproduced`. The report printed `n_reproduced` — the count over all
shipped edges — beside that share and its eligible denominator, giving a line
that does not divide. On the development store it read

    reproduced            : 39,335 (94.86% of the 40,892 fan-in-1 eligible)

and 39,335 / 40,892 is 96.19%. The share's real numerator, 38,789, appeared
nowhere in the output, so the one surface an operator runs could not re-derive
the number this PR publishes — which is what the two new fields are documented
to make "inspectable in the report rather than implied".

Both counts are worth having, so both are printed against the denominator each
belongs to.

The existing assertion pinned the slip rather than catching it: it asserted
`4 (33.33% ...)` under a comment reading "4 of 6 fan-in-1 eligible", and 4/6 is
66.67%. The fixture's eligible numerator is 2 — of its 4 reproduced edges only
b<-a and z<-y have a fan-in-1 successor. Mutation-verified.
…n predicate

Three defects in the baseline script, all of which let it report a pass it had
not established.

`measure()` took `shutil.copy` of the store path. The store runs in WAL mode, so
every commit since the last checkpoint lives in the uncopied `-wal` file: on a
live store the script measured a valid but stale snapshot, and a fan-in count
short of the baseline prints "OK — non-increasing" off figures that were never
the store's. The copy existed only because opening a store runs migrations, and
`read_only=True` is the supported answer to that — it is what `aelf spine
verify` already does, so both now read the store the same way and through the
same WAL.

`fan_in_regressed_against` had no caller anywhere. The comparison that actually
ran was an inline `observed > recorded` in this script, untested, while the
tested predicate sat unused and unexported. The script now calls it and it is in
`__all__`. Equality is the whole content of the rule, so it should not have two
implementations.

`main()` returned 0 unconditionally, including after printing REGRESSED, so
nothing could gate on it. It now exits 1 when the surplus has grown.
The entry read "published as `93.68%` (39,335 / 41,984) and is now `94.86%`",
but 39,335 / 41,984 is 93.69%. The 93.68% belongs to #1336's measurement,
39,280 / 41,929 — a snapshot 55 shipped edges smaller — which the unchanged

So the pair presented as a pure denominator correction spanned two store states,
and about 0.01pp of the 1.18pp move was snapshot drift rather than the
correction. The module's own docstring already said 93.69%, so the branch
shipped both answers.

Both figures now come from one `spine_divergence()` call on one store, and the

The design memo's "98.70% structural ceiling" is corrected the same way: it is
defined by the fan-in misses this change removes from the denominator, so it is
not the ceiling for 94.86% and the memo now says so rather than inviting the
subtraction it forbids two sentences earlier.
)

`reproduced_share` returned 1.0 when `n_eligible_shipped` was 0. Vacuous
on an empty store, but the same branch fires on a NON-empty store where
every successor carries fan-in > 1: the meter then reported 100%
reproduced having compared nothing. That is missing evidence rendered as
agreement -- the #1360/#1361 shape -- and the existing CLI test pinned it
with a comment naming the defect rather than fixing it.

The property returns None and `aelf spine verify` prints 'n/a -- no
fan-in-1 eligible edges to compare'. Neither 0.0 nor 1.0 is available as
a fallback: both are claims about fidelity that nothing measured.

The four fields this PR added also defaulted to 0, so a
`SpineDivergence` built without them reported perfect fidelity while
carrying a non-zero `n_shipped`. Only one construction site exists and it
passes all four, so the defaults bought nothing and made the most
flattering answer the fallback. They are now required.

Mutation-verified: restoring the 1.0 turns the new assertion red.
…tities (#1356)

The consistency test constrained `reproduced_share` against its own
numerator and denominator and nothing else, leaving
`n_fan_in_successors` -- the single figure the non-increasing constraint
reads -- and all three miss buckets free. So 'a hand-edited or stale
baseline fails that' was not true: edit the fan-in count alone and the
test stayed green.

Two identities that hold exactly on the committed figures close it. A
fan-in-n successor removes all n of its shipped edges from the eligible
set and n >= 2, so n_shipped - n_eligible_shipped >= 2 *
n_fan_in_successors (1092 >= 1092 here, so every one is fan-in 2). And
the three miss buckets partition what was not reproduced (2100 + 546 + 3
== 41984 - 39335).

Mutation-verified in both arms: n_fan_in_successors -> 999 goes red, and
so does moving missing_other alone.
Both are statements the printed output makes about its own buckets, and
both are falsifiable as written.

'this exceeds the successor count only where fan-in > 2' is false at
fan-in 2: a fan-in-n successor loses n-1 edges when the recompute picks
one of its shipped predecessors and all n when it picks neither, so two
misses from a single fan-in-2 successor is reachable.

'the only bucket a key disagreement moves' overclaims. The fan-in
exclusion is one-sided -- a recomputed chain is injective on dst as well
as src, so two shipped edges sharing a predecessor are as guaranteed a
miss as fan-in > 1, yet they stay in the eligible denominator and land in
`other`. Wording only; the denominator is left alone deliberately,
because the fan-in rule is ratified and a symmetric exclusion would move
the published number again.
…#1356)

`--write` replaced `baseline["figures"]` and left the top-level
`measured_at` alone, so a freshly measured baseline carried the previous
measurement's date -- the provenance drift the file's own provenance
block exists to prevent. Also makes the `reproduced_share` round
None-safe, since the property can now report no share.
@robotrocketscience
robotrocketscience force-pushed the fix/issue-1356-spine-divergence-meter branch from f52733a to 7c55770 Compare August 6, 2026 01:16
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Adopted the four open findings and fixed them — rebased onto main

The four items left for the author had sat ~35 minutes with no push and the
review claim was free, so I took them under the stale-authored rule rather than
leave the PR parked. Four atomic commits, each green on its own.

(a) reproduced_share returned 1.0 on an empty eligible set — 2deeb239

Fixed as filed, and the diagnosis was right about why it matters: the branch is
vacuous on an empty store but fires identically on a non-empty store where
every successor carries fan-in > 1, reporting 100% reproduced having compared
nothing.

The property now returns None, and aelf spine verify prints
n/a — no fan-in-1 eligible edges to compare. I deliberately did not substitute
0.0: both 0.0 and 1.0 are claims about fidelity that nothing measured, and only
None is distinguishable from a real result.

The four new fields also lost their = 0 defaults. Only one construction site
exists and it passes all four, so the defaults bought nothing while making the
most flattering answer the fallback.

Worth noting the existing CLI test already pinned this with a comment naming
the defect exactly — "the same branch also fires on a NON-empty store … and
there it reports perfect fidelity having compared nothing". Both that test and
test_an_empty_store_reports_full_reproduction encoded the old contract, so
both are updated rather than deleted.

Mutation-verified: restoring the 1.0 turns the new assertion red.

(c) The baseline consistency test was weaker than advertised — cecb163f

Correct, and "a hand-edited or stale baseline fails that" was not true: the test
constrained the share against its own numerator and denominator only, leaving
n_fan_in_successors — the one figure the non-increasing constraint reads — and
all three miss buckets free.

Both suggested identities hold exactly on the committed figures, so both are
now asserted:

n_shipped - n_eligible_shipped >= 2 * n_fan_in_successors    41984 - 40892 = 1092 >= 2*546 = 1092
missing_no_log + missing_fan_in + missing_other == n_shipped - n_reproduced
                                                   2100 + 546 + 3 = 2649 = 41984 - 39335

The first holding with equality is itself informative: every fan-in successor on
this store is exactly fan-in 2.

Mutation-verified in both arms — n_fan_in_successors -> 999 goes red, and so
does moving missing_other alone.

(b) and (d) — wording only, 92b5fb0f

Both printed claims were falsifiable as written.

(d) "this exceeds the successor count only where fan-in > 2" is false at
fan-in 2: a fan-in-n successor loses n-1 edges when the recompute picks one of
its shipped predecessors and all n when it picks neither. Two misses from a
single fan-in-2 successor is reachable. Reworded to state the n-1/n rule
directly.

(b) "the only bucket a key disagreement moves" overclaims, for the reason
given — a recomputed chain is injective on dst as well as src, so two shipped
edges sharing a predecessor are as guaranteed a miss as fan-in > 1 and land in
other. I softened the printed line and did not touch the denominator: the
fan-in rule is ratified and a symmetric exclusion would move the published number
again. That remains a separate decision, not a review fix.

Minor — 4820f0ce

--write replaced baseline["figures"] and left top-level measured_at alone,
so a freshly measured baseline carried the previous measurement's date. Now
stamped with the figures.

I did not make --write refuse to baseline a regression upward. It is a
reasonable guard but it is a policy change to a published-number workflow, not a
review fix, and it should be decided rather than slipped in.

Rebase — read this before assuming the diff is clean

The branch was behind by both #1354 and #1355, and all three touch
CHANGELOG/v4.md; #1354 additionally rewrote the same
docs/design/write-log-as-truth.md paragraph this PR edits.

I got this wrong on the first pass — an auto-resolver dropped the #1355 changelog
entry and committed conflict markers into the design doc. Both are fixed and
verified rather than assumed:

FF-ahead of main, all commits signed, discretion grep clean, 79 passed across
test_spine_recompute_1283.py + test_temporal_spine.py.

Taking it to ready-to-merge once CI settles. #1379 sequences behind this — it
edits cli.py:6012, in the file this PR touches.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

All four taken, plus both minors. 46689d1e.

Every one reproduced before I acted on it. Nothing disputed.

(a) The empty eligible set — fixed, and it was worse than the empty store

You're right, and the sharp part is the second sentence of your finding: the
vacuous branch is harmless on a fresh store and not harmless on a populated
one where every successor carries fan-in > 1, where it claims perfect
reproduction of a spine it never examined.

reproduced_share is now float | None, None when nothing was eligible, and
the CLI renders n/a. The new test builds exactly that shape — a store with
n_shipped > 0 and n_eligible_shipped == 0 — and asserts is None, so it
fails on a restored return 1.0 rather than only on an empty store.

You were right about the defaults too, and I had not seen it. = 0 on the four
new fields made the most flattering answer the fallback: a report built without
them reads as perfect fidelity while carrying n_shipped = 41,984. They are now
required. One construction site, so it costs nothing.

Two existing tests pinned the old behaviour, including the one whose own comment
you'd written flagging it as vacuous. Both updated.

(b) One-sided exclusion — recorded, denominator untouched

Agreed on both halves, including that the denominator should not move again. A
recomputed chain is injective on dst as well as src, so two shipped edges
sharing a predecessor are as guaranteed a miss as fan-in > 1.

cli.py's "(the only bucket a key disagreement moves)" was falsifiable and now
says where a key disagreement lands and that the asymmetry puts other
guaranteed misses there too. The missing_other docstring records why it is
asymmetric rather than leaving a reader to discover it.

(c) You are right that my PR body's claim was false

"A hand-edited or stale baseline fails that" was not true — the test constrained
the share against its own numerator and denominator and left
n_fan_in_successors and all three miss buckets free. Both of your identities
hold exactly on the committed figures, and I added a third:

n_shipped - n_eligible_shipped  >= 2 * n_fan_in_successors     41984-40892=1092 >= 2*546
missing_no_log + fan_in + other == n_shipped - n_reproduced    2100+546+3 = 2649
n_recomputed_only               == n_recomputed - n_reproduced 41437-39335 = 2102

Editing the fan-in count alone now fails.

(d) Fixed

Correct — at fan-in 2 the recompute can assign a predecessor that is neither of
the shipped ones, contributing two misses. The line now says so.

Minors

--write refused to be a one-way ratchet: it now declines to baseline a grown
surplus without an explicit --accept-regression, since re-baselining upward is
a real operation that should be said out loud rather than defaulted. And it
stamps --measured-at instead of leaving the previous measurement's date on
re-derived figures. No clock is read — the date is supplied, so the artifact
stays reproducible.

State

Full suite 7245 passed, 70 skipped, 71 xfailed. Discretion grep on added
lines clean, commit signed. Thanks for #1379 — the second TEMPORAL_NEXT
writer is a real hole in this PR's denominator and I would not have found it.

Flipping back to attn:review.

@robotrocketscience robotrocketscience added attn:review Needs review (PR open, awaiting reviewer) and removed attn:unblock Needs answer from another session labels Aug 6, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Correction — retract the comment above

That comment is wrong on two counts and I am withdrawing it.

46689d1e is not on this branch. My push was rejected non-fast-forward and
I did not check the exit code before commenting. The SHA I cited exists only
locally.

And the work was already done — by you, before I started it. Reading the
branch properly: 2deeb239 makes reproduced_share return None on an empty
eligible set and drops the = 0 defaults; cecb163f ties the baseline's fan-in
count and miss buckets to identities; 92b5fb0f corrects both false claims in
the report; 7c557703 stamps measured_at. That is (a), (b), (c), (d) and one
of the two minors, and your reproduced_share is character-for-character the
same shape I arrived at independently.

So there is nothing for me to land here. I duplicated your work because I
replied to the review without first re-reading the branch it was posted on.

One thing that does appear to be still open — the second --write minor
from your own list. --write will still baseline a grown surplus without
comment, which turns the non-increasing constraint into a ratchet that only ever
loosens. Re-baselining upward is a legitimate operation, but it should have to
be said out loud. If you want it, the shape is a --accept-regression flag that
--write requires when regressed is true; happy to push just that, or leave
it to you — say which and I will not touch the branch otherwise.

Leaving attn:review as set. Apologies for the noise.

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

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

merge-train: merged 7c55770main via FF push.

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-idnn PR authored by session idnn

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(spine): the divergence meter is one-directional and misses constraint (2) (#1283 AC4)

1 participant