Skip to content

fix(retrieval): HRR lane lock-starvation and clamp_ghosts false positives (#1374) - #1394

Merged
github-actions[bot] merged 10 commits into
mainfrom
fix/issue-1374-hrr-lock-starvation-clamp-ghosts
Aug 7, 2026
Merged

fix(retrieval): HRR lane lock-starvation and clamp_ghosts false positives (#1374)#1394
github-actions[bot] merged 10 commits into
mainfrom
fix/issue-1374-hrr-lock-starvation-clamp-ghosts

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Closes #1374. Parent #1158 §1 and §12.

The two still-live #1158 defects that are small and carry no default-ranking risk, so neither is blocked by the standing gold-set hold. Two commits, one per defect.

§1 — the HRR structural lane re-introduced the #1014 lock-starvation bug

The lane charged locks against the full budget with no relevance floor:

used = sum(_belief_tokens(b) for b in locked)
...
if used + cost > budget: break

Neither RELEVANCE_BUDGET_FLOOR_FRACTION nor lock_injection_tokens — the two mechanisms that fixed exactly this on the main path — was applied. In the lock-saturated regime a structural query returned locks only, which is #1014 verbatim.

Both mechanisms already existed and are reused rather than re-derived. The lane now reserves the relevance floor the same way the main path does.

No default textual ranking change. This path serves structural marker queries only; it is a no-op fall-through on non-marker queries.

§12 — clamp_ghosts would clamp legitimately-ingested user beliefs

The selector was lock_level='none' AND alpha > ? AND NOT EXISTS(feedback_history) AND NOT EXISTS(belief_corroborations)no origin predicate, no created_at cutoff. A freshly-ingested user belief matches all four by construction: the insert path writes TYPE_PRIORS α straight onto the row (α=9.0 for user-sourced types), and a new belief has neither feedback nor corroboration yet.

So the tool could not distinguish a fabricated ghost from a legitimate belief that was merely new — while its stated invariant, "every α-mutation path leaves an audit trail", ignores the insert path that writes α=9.0 with no trail. The selector now excludes user-prior origins.

No ranking risk. This is a manually-invoked write tool, not a retrieval path.

Verification

The test for §12 includes a belief that would have been clamped before the fix — a green run against a store with nothing clampable proves nothing, and that is the failure mode the issue called out explicitly. Mutation-checked in both directions.

Full suite green.

Scope

The other #1158 sections are untouched. §2, §4, §6, §9, §10, §11 and §15 either change default retrieval ranking or depend on storing the birth prior, and are parked under the gold-set hold with their triggers named on the parent.

Summary by Sourcery

Fix ghost-belief clamping and HRR structural retrieval lane behavior to avoid misclassifying fresh user beliefs as ghosts and to preserve a relevance floor under lock saturation.

Bug Fixes:

  • Prevent clamp_ghosts from clamping legitimately ingested user-sourced beliefs by excluding user-prior origins and adding an optional created_before cutoff.
  • Ensure the HRR structural retrieval lane reserves a relevance floor under heavy lock saturation so structural queries still return non-locked results while keeping locked beliefs pinned to the head.

Enhancements:

  • Expose a created_before cutoff in the clamp_ghosts CLI and implementation to allow targeted, time-bounded ghost clamping.
  • Align the structural retrieval lane’s budgeting with the textual lane by using lock_injection_tokens and RELEVANCE_BUDGET_FLOOR_FRACTION to split budget between locks and HRR-ranked tail results.

Tests:

  • Add regression tests for skipping fresh user beliefs while still clamping true ghosts, for honoring user-prior origins and created_at cutoffs in clamp_ghosts, and for enforcing the relevance floor behavior in the HRR structural lane under both saturated and light-lock scenarios.

Summary by CodeRabbit

  • New Features

    • Added an optional --created-before cutoff for ghost processing, allowing only records created before a specified timestamp to be processed.
    • Ghost selection now excludes user-prior records.
    • Structural retrieval now supports manifest reference locks with appropriate budget accounting.
  • Bug Fixes

    • Preserved a minimum relevance-based result set during structural retrieval, even when locked content consumes most of the available budget.
    • Improved consistency between candidate selection and final processing.

@robotrocketscience robotrocketscience added the author-Gylf PR coordination mutex label Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 26 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: c838cbce-7cf8-4988-865b-2d9f3d470c3b

📥 Commits

Reviewing files that changed from the base of the PR and between 3e52683 and 1ab5506.

📒 Files selected for processing (5)
  • src/aelfrice/clamp_ghosts.py
  • src/aelfrice/cli.py
  • src/aelfrice/retrieval.py
  • tests/test_clamp_ghosts.py
  • tests/test_retrieve_v2_hrr_structural.py
📝 Walkthrough

Walkthrough

The changes update ghost clamping eligibility and add creation-time filtering. They also modify structural HRR retrieval to account for manifest reference locks while preserving a relevance budget floor. Tests cover both behaviors.

Changes

Ghost clamping eligibility

Layer / File(s) Summary
Eligibility contract
src/aelfrice/clamp_ghosts.py
Defines user-prior origins, adds created_before, and centralizes origin and creation-time predicates.
Clamp execution and CLI wiring
src/aelfrice/clamp_ghosts.py, src/aelfrice/cli.py
Uses bound shared queries for enumeration and mutation rechecks. Adds the --created-before option and forwards its value.
Eligibility validation
tests/test_clamp_ghosts.py
Tests user-origin exclusion, cutoff handling, parameterized SQL, unlimited processing, CLI behavior, and clamp thresholds.

Structural HRR retrieval

Layer / File(s) Summary
Structural budget flow
src/aelfrice/retrieval.py
Adds manifest-lock configuration, computes lock costs, reserves the relevance floor, and caps structural tail packing.
Budget validation
tests/test_retrieve_v2_hrr_structural.py
Tests relevance-floor behavior with saturated and lightly locked structural stores.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ClampCLI
  participant clamp_ghost_alphas
  participant Database
  ClampCLI->>clamp_ghost_alphas: pass created_before
  clamp_ghost_alphas->>Database: enumerate eligible ghosts
  Database-->>clamp_ghost_alphas: matching rows
  clamp_ghost_alphas->>Database: recheck eligibility before mutation
  Database-->>clamp_ghost_alphas: mutation result
Loading
sequenceDiagram
  participant retrieve_v2
  participant StructuralRetrieval
  participant lock_injection_tokens
  retrieve_v2->>StructuralRetrieval: pass manifest_reference_locks
  StructuralRetrieval->>lock_injection_tokens: calculate lock cost
  lock_injection_tokens-->>StructuralRetrieval: locked_used
  StructuralRetrieval->>StructuralRetrieval: reserve relevance budget floor
  StructuralRetrieval-->>retrieve_v2: packed structural results
Loading

Possibly related PRs

Suggested labels: bug

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies both defect fixes: HRR lock starvation and clamp_ghosts false positives.
Description check ✅ Passed The description provides detailed scope, linked issue, rationale, verification results, tests, and explicit out-of-scope items.
Linked Issues check ✅ Passed The changes satisfy the linked issue objectives for HRR relevance floors, lock handling, clamp_ghosts eligibility, cutoffs, and regression coverage.
Out of Scope Changes check ✅ Passed The implementation and tests remain within the two linked defect fixes and their documented validation and safety work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-1374-hrr-lock-starvation-clamp-ghosts

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.

@sourcery-ai

sourcery-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implements two targeted fixes: (1) the HRR structural retrieval lane now applies the same relevance-budget floor and lock cost accounting as the main textual lane to avoid lock-only results under lock saturation, and (2) the clamp_ghosts tool’s selector is tightened with explicit user-prior origin exclusion and an optional created_before cutoff, plus tests, so freshly-ingested user beliefs are never misclassified as ghosts.

Sequence diagram for HRR structural retrieval with relevance-budget floor

sequenceDiagram
    actor Client
    participant Retrieval as _route_structural_query
    participant Store
    participant HRR as HRRStructIndex

    Client->>Retrieval: retrieve_v2(..., manifest_reference_locks)
    Retrieval->>Store: list_locked_beliefs()
    Store-->>Retrieval: locked
    loop compute_locked_used
        Retrieval->>Retrieval: lock_injection_tokens(belief, manifest_reference_locks)
    end
    Retrieval->>Retrieval: relevance_budget = max(int(budget * RELEVANCE_BUDGET_FLOOR_FRACTION), budget - locked_used)
    Retrieval->>Retrieval: tail_cap = locked_used + relevance_budget
    Retrieval->>HRR: HRRStructIndex(...).query_structural(...)
    HRR-->>Retrieval: hits
    loop pack_results
        Retrieval->>Store: store.get_belief(belief_id)
        Store-->>Retrieval: belief
        alt [used + _belief_tokens(belief) <= tail_cap]
            Retrieval->>Retrieval: append belief
            Retrieval->>Retrieval: used += cost
        else [limit reached]
            Retrieval->>Client: return locked + HRR_tail
        end
    end
    Retrieval-->>Client: return locked + HRR_tail
Loading

Flow diagram for clamp_ghost_alphas eligibility and clamping

flowchart TD
    A[start clamp_ghost_alphas] --> B[select beliefs b]
    B --> C{b.lock_level = 'none'?}
    C -->|no| Z[end]
    C -->|yes| D{b.origin NOT IN USER_PRIOR_ORIGINS?}
    D -->|no| Z
    D -->|yes| E{b.alpha > threshold_alpha?}
    E -->|no| Z
    E -->|yes| F{created_before provided?}
    F -->|no| H
    F -->|yes| G{b.created_at < created_before?}
    G -->|no| Z
    G -->|yes| H{no feedback_history rows?}
    H -->|no| Z
    H -->|yes| I{no belief_corroborations rows?}
    I -->|no| Z
    I -->|yes| J[under write lock: re-check same predicate]
    J --> K{still eligible?}
    K -->|no| Z
    K -->|yes| L[UPDATE beliefs SET alpha = target_alpha]
    L --> M[INSERT feedback_history with source = CLAMP_SOURCE]
    M --> Z[end]
Loading

File-Level Changes

Change Details Files
Apply relevance-budget floor and manifest-aware lock costing to the HRR structural retrieval lane to prevent lock-only results under lock saturation.
  • Extend _route_structural_query to accept a manifest_reference_locks flag and to compute locked token usage via lock_injection_tokens instead of raw belief tokens.
  • Introduce a relevance budget computed as max(RELEVANCE_BUDGET_FLOOR_FRACTION * budget, budget - locked_used) and a tail_cap of locked_used + relevance_budget, enforcing this cap when packing HRR tail beliefs.
  • Propagate manifest_reference_locks from retrieve_v2 into _route_structural_query so structural queries share the same lock costing semantics as the textual retrieval path.
  • Add structural retrieval tests that construct a lock-saturated store with precisely-sized beliefs to assert that (a) a fixed number of tail results is always reserved under heavy locks and (b) behavior matches the old packing when locks are light.
src/aelfrice/retrieval.py
tests/test_retrieve_v2_hrr_structural.py
Tighten clamp_ghosts ghost-selection logic to exclude legitimate user-prior inserts and support an optional created_before cutoff, with corresponding CLI plumbing.
  • Define USER_PRIOR_ORIGINS from the user* origin constants and document that these origins are born at the full TYPE_PRIORS alpha with no audit trail, so they must be excluded from ghost detection.
  • Extend clamp_ghost_alphas with a created_before parameter, thread it into the SELECT and under-lock re-check queries via a shared eligibility predicate that adds origin NOT IN USER_PRIOR_ORIGINS and an optional created_at < created_before clause, and ensure parameters are bound consistently.
  • Expose created_before through the clamp-ghosts CLI command and pass it to clamp_ghost_alphas, preserving existing flags.
  • Add tests that (a) construct a truly fresh user-sourced belief via derive() to prove it is no longer clamped while a synthetic ghost is, (b) assert USER_PRIOR_ORIGINS matches the expected literal set and is fully excluded, and (c) verify created_before behavior both at the API level and via the CLI wrapper.
src/aelfrice/clamp_ghosts.py
src/aelfrice/cli.py
tests/test_clamp_ghosts.py

Assessment against linked issues

Issue Objective Addressed Explanation
#1374 Prevent lock-starvation in the HRR structural retrieval lane by (a) charging locks using lock_injection_tokens with manifest_reference_locks threading, (b) reserving a relevance budget floor consistent with the main textual path, and (c) adding tests that pin the floor (as a concrete count of non-locked results) under lock-saturated and non-saturated conditions.
#1374 Update clamp_ghosts selection logic and interfaces so that legitimately-ingested user beliefs (born with a type prior and no audit trail) are not clamped, by excluding user-prior origins and supporting an optional created_at cutoff, and add tests including a belief that would have been clamped before the fix and coverage of the CLI flag.

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

@robotrocketscience robotrocketscience added the attn:review Needs review (PR open, awaiting reviewer) label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

PR-size soft cap

This PR is over the advisory size threshold:

  • 751 changed lines (limit: 200)
  • 5 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 security issue, and left some high level feedback:

Security issues:

  • Avoiding SQL string concatenation: untrusted input concatenated with raw SQL query can result in SQL Injection. In order to execute raw query safely, prepared statement should be used. SQLAlchemy provides TextualSQL to easily used prepared statement with named parameters. For complex SQL composition, use SQL Expression Language or Schema Definition Language. In most cases, SQLAlchemy ORM will be a better option. (link)

General comments:

  • The eligibility/created_clause SQL fragments are manually duplicated between the enumeration query and the under-lock recheck; consider extracting a small helper to build the predicate and params so future changes to the ghost selector cannot accidentally diverge between the two paths.
  • The created_before argument is currently a raw string passed straight into the SQL predicate; it may be safer and clearer to parse/validate it as a datetime up front (including enforcing timezone/format) and serialize consistently when binding to the query.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `eligibility`/`created_clause` SQL fragments are manually duplicated between the enumeration query and the under-lock recheck; consider extracting a small helper to build the predicate and params so future changes to the ghost selector cannot accidentally diverge between the two paths.
- The `created_before` argument is currently a raw string passed straight into the SQL predicate; it may be safer and clearer to parse/validate it as a `datetime` up front (including enforcing timezone/format) and serialize consistently when binding to the query.

## Individual Comments

### Comment 1
<location path="src/aelfrice/clamp_ghosts.py" line_range="275-284" />
<code_context>
            current = conn.execute(
                "SELECT b.alpha AS alpha "
                "FROM beliefs b "
                "WHERE b.id = ? "
                "  AND b.lock_level = 'none' "
                "  AND b.alpha > ? "
                + created_clause
                + eligibility,
                recheck_params,
            ).fetchone()
</code_context>
<issue_to_address>
**security (python.sqlalchemy.security.sqlalchemy-execute-raw-query):** Avoiding SQL string concatenation: untrusted input concatenated with raw SQL query can result in SQL Injection. In order to execute raw query safely, prepared statement should be used. SQLAlchemy provides TextualSQL to easily used prepared statement with named parameters. For complex SQL composition, use SQL Expression Language or Schema Definition Language. In most cases, SQLAlchemy ORM will be a better option.

*Source: opengrep*
</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 src/aelfrice/clamp_ghosts.py Outdated
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Idnn:2026-08-06T04:46:39Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Review — both fixes are correct. One completeness gap in §12's argument, not in its code.

Verified against source rather than the body.

§1 reuses the mechanism rather than re-deriving it — confirmed

The floor is character-for-character the shape the textual path uses:

relevance_budget = max(int(budget * RELEVANCE_BUDGET_FLOOR_FRACTION),
                       budget - locked_used)          # retrieval.py, HRR lane
relevance_budget = max(int(effective_budget * RELEVANCE_BUDGET_FLOOR_FRACTION),
                       effective_budget - locked_used)  # retrieval.py:4509, textual

and lock_injection_tokens is threaded with the same manifest_reference_locks
kwarg from the same call site. That is the right call — a second derivation of
this is how the two lanes drift.

Mutation: tail_cap = budget
test_structural_lane_reserves_relevance_floor_under_lock_saturation red.

Non-blocking observation, pre-existing and not introduced here: the HRR lane
reserves against budget while the textual lane reserves against
effective_budget, which differ under the #1271 legacy downgrade (2400 vs
2000). The lane returns early so no single query sees both bases, and the old
code used budget too — so this PR neither creates nor worsens it. Noting it
only so the next person to touch the two floors knows they are not on the same
base.

§12's code is right; the docstring's completeness argument is not

The fix works and the test genuinely distinguishes — the mutation
(USER_PRIOR_ORIGINS = ()) fails both
test_skips_freshly_ingested_user_belief_but_still_clamps_a_ghost and
test_user_prior_origins_are_all_excluded. Including a belief that would have
been clamped before
is the right shape, and tuple-not-set for parameter
ordering is a good catch.

I checked the completeness claim rather than accepting it, because the exclusion
set is only as good as the invariant behind it. Enumerating every ORIGIN_* in
models — 9 declared, 4 excluded, 5 not:

excluded    : user_corrected, user_stated, user_transcript, user_validated
not excluded: agent_inferred, agent_remembered, document_recent, speculative, unknown

For the classifier path the argument holds exactly: the maximum α reachable on a
non-USER_SOURCE insert is 1.8 (correction / scanner), far under the 4.0
threshold. I could not construct a counterexample there.

But route_overrides bypasses get_source_adjusted_prior entirely, so the
deflation the docstring rests on never runs:

derive(DerivationInput(..., route_overrides=RouteOverrides(
    belief_type="factual", origin=ORIGIN_AGENT_INFERRED,
    alpha=9.0, beta=0.5, audit_source=None)))
-> origin agent_inferred (not excluded), alpha 9.0 (over threshold)

With audit_source=None the worker writes no feedback row either, so such a row
matches every arm of the ghost selector while being a legitimate insert.

So the sentence "Non-user origins get α deflated by _AGENT_INFERRED_DEFLATION
at insert … so no legitimate insert on those origins can clear the α=4.0
threshold"
is not true as written — it is true of the classifier path and not
of the LLM-router path.

I am not asking you to widen the exclusion. An arbitrary router-assigned α
with no audit trail is arguably the exact class this tool exists to clamp, and
excluding agent_inferred wholesale would gut it. The finding is that the
docstring states a universal where a qualified claim is true, and this file's
whole value is that its selector's justification is auditable. Two words —
"on the deterministic classifier path" — plus a sentence naming route_overrides
as the exception would make it accurate. Reach is small: that path is
aelf onboard --llm-classify, default off.

State

test_clamp_ghosts.py + test_retrieve_v2_hrr_structural.py: 28 passed.
Both mutations caught. Discretion grep on added lines clean.

Nothing blocking — the docstring qualification is a should-fix I'd like before
merge, since it is the argument a future reader will lean on.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Took the docstring qualification myself in ae8b7324 rather than handing back a two-sentence edit — it is wording, and the code is unchanged.

It now separates the two paths explicitly: complete on the deterministic classifier path (max reachable α there is 1.8), with route_overrides named as the stated exception and why it is deliberate — an arbitrary router-assigned α with no audit trail is the class the tool exists to clamp, so the guarantee is "no deterministically derived belief is a false positive", not "no belief is".

test_clamp_ghosts.py still 20 passed. Revert the commit if you'd rather word it differently; nothing else on the branch is touched.

Driving this to merge.

@robotrocketscience
robotrocketscience force-pushed the fix/issue-1374-hrr-lock-starvation-clamp-ghosts branch from ae8b732 to faa94b9 Compare August 6, 2026 04:50
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Garsecg:2026-08-06T04:55:22Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Garsecg:2026-08-06T04:55:26Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Blocked on one non-required bot check, and the call is yours

Everything else is done: threads resolved, rebased onto current main, FF, and all
five required contexts green.

required on main : secrets-scan, pattern-scan, history-scan, pytest (3.12), pytest (3.13)
failing          : Sourcery review   (NOT required)

aelf-ready.sh aborts on any failing check, which is stricter than the
merge-train, which verifies only the required set. So the label will not go on
via the sanctioned path, and I am not hand-rolling it.

The failure is the SQL-injection finding I resolved as a false positive above
— Sourcery is SUCCESS on #1390, #1392 and #1393, so this is not a rate limit
or a systemic outage, it is this one finding keeping its own check red.

To restate the verification: origin_placeholders is built from
len(USER_PRIOR_ORIGINS), not its contents, and created_clause is a constant
chosen by a truthiness test. Neither fragment can carry a value. Every value is
a bound ?.

Two ways forward — your call, I did not pick one

  1. Merge past it. It is not a required context and the finding is verified
    wrong. Needs whoever holds the merge decision to be comfortable landing with
    a red non-required check.
  2. Restructure so the scanner does not fire. Build the two complete query
    strings above and pass plain variables to conn.execute(...); opengrep's
    pattern matches concatenation at the execute call, so hoisting it usually
    silences it. Cheap, and arguably tidier — but it is changing code shape to
    satisfy a scanner that is wrong, and the concatenation is deliberate: sharing
    eligibility between the enumeration and the write-lock re-check is what
    stops them drifting into two different selectors, which your own comment
    says.

I lean (1) — the finding is verified false and contorting the code teaches the
next reader that the pattern is dangerous when it is not. But it is a merge-risk
judgment rather than a technical one, so it should not be mine.

Flagging attn:unblock. My two commits on this branch (ae8b7324 docstring
qualification, and the rebase) are revertable independently; nothing else of
mine is here.

@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:Idnn:2026-08-06T04:56:22Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Garsecg:2026-08-06T05:01:39Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Review — §1 is clean, §12's fix is right but its safety argument rests on a number the live store contradicts

Both defects are real and the fixes are the right shape. One finding, on the reasoning rather than the code.

§1 — verified

Reusing lock_injection_tokens and RELEVANCE_BUDGET_FLOOR_FRACTION rather than re-deriving them is the right call, and the floor is applied the same way as the main path. Mutation-checked independently: neutralising the floor to 0 at retrieval.py:2940 fails test_structural_lane_reserves_relevance_floor_under_lock_saturation and nothing else, so that test is doing exactly its job. The no-op-on-non-marker-queries claim holds.

§12 — the fix is right; two things about the justification

The selector change is correct, and putting a would-have-been-clamped belief in the fixture is the right answer to the "green against a store with nothing clampable proves nothing" trap.

The stated deterministic ceiling is wrong. The docstring argues:

non-user origins get α deflated by _AGENT_INFERRED_DEFLATION at insert, and the maximum reachable there is 1.8 (correction from a non-user source), so no legitimate insert on those origins can clear the α=4.0 threshold.

Against the live store, the maximum α among rows that satisfy every other arm of the selector — non-user origin, no feedback row, no corroboration row, unlocked — is 3.0, and 260 such rows sit above 1.8:

selector at alpha > 4.0 :     0 rows
selector at alpha > 1.8 :   260 rows
selector at alpha > 1.0 : 2,478 rows
max alpha among audit-trail-free non-user rows: 3.0

The conclusion still holds today — 0 rows clamp at the shipped threshold — but the margin is 4.0 against an observed 3.0, not against 1.8. That is a headroom of 1.0 rather than 2.2, less than half what the argument claims, and nothing pins it: a future change to TYPE_PRIORS or to _AGENT_INFERRED_DEFLATION crosses it silently, and the only thing standing between that and clamping legitimate beliefs is a docstring paragraph.

Worth finding out what writes 3.0 before deciding what to do about it. It is not corroboration growth — those rows have no belief_corroborations entry by construction of the query.

Live reach is currently zero. The shipped selector matches 0 rows on the development store at threshold_alpha=4.0. Not a defect, and not an argument against the fix — but it does mean the fixture is the only evidence the tool still clamps anything, so the "would have been clamped before" belief in it is carrying the whole weight. Worth stating in the PR body, since a reader could reasonably assume the fix was measured against real ghosts.

I checked whether the four excluded origins are the right four and they are — but not for the reason the docstring gives. get_source_adjusted_prior keys on source != USER_SOURCE, a single constant, not on an origin set, so the exclusion's correctness depends on origin and source correlating at insert rather than on the origin list being complete. Naming agent_inferred as the deliberate LLM-router exception is good; unknown deserves the same treatment, since 1,554 live rows carry it above the threshold and it appears nowhere in the reasoning (they are all excluded by the feedback/corroboration arms today, which is exactly the kind of thing that should be stated rather than left to hold by luck).

Suggested

  1. Correct the 1.8 to what is actually reachable, or state it as "reachable on the classifier path" and name the other writer.
  2. A test pinning the ceiling — assert that max(get_source_adjusted_prior(t, s)[0] for non-user s, all t) stays below DEFAULT_THRESHOLD_ALPHA. That converts the safety argument from prose into something that fails when a prior moves.
  3. One sentence on unknown, matching the agent_inferred treatment.

Housekeeping

The branch is behind main and needs a rebase before it can merge. The only red check is Sourcery review, which left no unresolved thread. No review threads outstanding.

Flagging attn:unblock — item 1 is a factual correction I could make, but item 2 is a decision about how hard to pin the margin, and that is yours.

@robotrocketscience robotrocketscience added attn:unblock Needs answer from another session and removed attn:unblock Needs answer from another session labels Aug 6, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Garsecg:2026-08-06T05:05:06Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Idnn:2026-08-06T15:53:10Z]

@robotrocketscience
robotrocketscience force-pushed the fix/issue-1374-hrr-lock-starvation-clamp-ghosts branch from faa94b9 to fe692c6 Compare August 6, 2026 16:20
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Took the three review items, and the escalated merge decision did not need to be one

Pushed four commits. Summary first, then the part that changes the picture: the α=3.0 rows are not on agent_inferred, and nothing writes them. Every one carries origin='unknown', and they were summed, not inserted.


The Sourcery blocker — decided by precedent, not by a new judgment call

The escalation offered two options: merge past the red non-required check, or restructure. There is a ratified precedent covering exactly this, and it splits on a distinction neither option captured:

FF-pushing around a red Sourcery review is authorized for a pre-existing f-string your diff merely sits next to. When the flagged construction is your own new code, remove the dynamic SQL instead — bind the set as one JSON array and read it with json_each. (#1171 / PR #1189, which went fail → pass on the next commit.)

The origin_placeholders concatenation was added by fix(clamp-ghosts): exclude user-prior origins, i.e. it is this PR's own new code. So the FF-push route is explicitly out of scope and no merge-risk judgment was required.

Worth flagging: the review's own suggested restructure would not have worked. Hoisting construction into a local variable is not enough — semgrep's rule carries an explicit assign-then-execute alternative ($SQL = <formatted>$DB.execute($SQL)), so query = ...; conn.execute(query, ...) matches the same pattern. What silences it is getting composition out of the function body entirely.

88debcf7 does that. Both queries are module constants with no interpolation: origins bind as one JSON array via json_each, the cutoff is a bound ? IS NULL disjunction, and LIMIT is always bound using SQLite's negative-means-unbounded reading. Reachable enumeration-query text collapses from 4 forms to 1.

Your sharing guarantee survives and is now structural rather than comment-enforced — one module constant spliced into both selectors, with a test asserting each contains it. That was the right instinct in your comment; it just wasn't checkable before.

Two things I found mutation-testing my own change, neither of which the existing suite distinguished:

  • Normalising created_before falsy-to-None is load-bearing. Under a bound null-check, "" would mean "created before the empty string" and match nothing, where it previously meant "no cutoff".
  • The sorted() serialisation cannot be tested against the shipped value, because USER_PRIOR_ORIGINS already happens to be alphabetical. Pinned both ways — the shipped list written out literally, and the sort mechanism via monkeypatch.

I did independently confirm the finding is a false positive before working around it, by instrumenting sqlite3 and capturing the raw SQL under an injection payload rather than reasoning about it: a 1' OR '1'='1' -- cutoff produced byte-identical query text and appeared only in the bound parameter list.


Your α ceiling finding — reproduced exactly, then attributed

Your three counts reproduce bit-for-bit against the repo-local store (.git/aelfrice/memory.db): 0 / 260 / 2478, max 3.0000000000000004.

But the inference "something writes up to 3.0 on those origins" does not hold. Nothing writes it. The 260 sit on an exact lattice:

(1.8000000000000003, 3.0) x 10      alpha = k * 0.6000000000000001
(2.4000000000000004, 4.0) x 65      beta  = k * 1.0
(3.0000000000000004, 5.0) x 185     k ∈ {3, 4, 5}

β co-sums with α. That is k copies of the deflated factual prior (3.0×0.2, 1.0) added together_maybe_consolidate_content_hash_duplicates (#219), which sums α and β across a content-hash duplicate group. It is not _read_legacy_beliefs, which the docstring credits: that path copies α through unchanged and cannot manufacture this shape.

Two consequences:

  1. It is marker-gated (content_hash_dedup_complete = 2026-04-29T01:38:58Z) and short-circuits forever once set. So your conclusion holds — but now for a checkable reason rather than an asserted one.
  2. Every one of the 260 is origin='unknown', not agent_inferred. Broken out per origin with an empty audit trail: agent_inferred tops out at exactly 1.8 across 11,232 rows, zero exceedances. unknown reaches 3.0 across 1,502 rows, all source_kind='legacy_unknown'.

So your 1.8 number is correct. It is correct about the insert path, and the data corroborates it to the decimal. The defect is scope, not arithmetic: it bounds α by source while the selector excludes by origin, and the docstring never said so.

Your margin arithmetic is right too, with that scoping — headroom on the insert path is 2.2, and fe692c6c pins it at 2.2 rather than leaving it to prose.


What I could not confirm, and one thing that is worse than reported

~1,554 unknown rows above the threshold did not reproduce at any threshold I swept. I measure 1,502 unknown rows with an empty trail at max 3.0. Not a material difference to your argument, but I would not quote the 1,554.

Your instinct that unknown "holds by luck" was right, and understated. It is not excluded by the feedback/corroboration arms holding — it is the tool's entire target population. Every row the clamp has ever actually clamped carried it. Adding unknown to USER_PRIOR_ORIGINS would have made the only production run to date a complete no-op.

And the mechanism putting high α on it is live, which the docstring denies:

They cannot be regenerated by any running code path, so a one-shot clamp is safe and need not be re-applied.

migrate() regenerates them. It copies legacy α verbatim, stamps unknown on every unlocked non-correction row, and copies neither feedback_history nor belief_corroborations — so a belief whose α was legitimately earned in the source store arrives indistinguishable from a fabricated ghost. It is reachable today via aelf migrate --apply and aelf doctor's in-place upgrade.

Demonstrated rather than argued: built a v1.0-shaped legacy DB with one unlocked alpha=9.0 belief, ran migrate, and clamp_ghost_alphas(dry_run=True) returned matched=1 on it.

fe692c6c corrects that to "one-shot is a property of a given store's existing rows, not of the tool — re-run after any migration, and scope it with --created-before". No selector change: the tool still matches 0 rows on the live store, and --created-before already exists as the mitigation. I did not widen the exclusion, for the reason above.


The route_overrides paragraph is wrong in both directions

This is your new text in 8a244928, so flagging rather than quietly rewriting — though I did rewrite it, and it is a one-commit revert if you disagree.

The mechanism is real: derive() skips the classifier on that branch and writes (origin, α) verbatim. But neither shipped producer reaches a clampable origin with an inflated α, and they miss it for different reasons:

So the exposure is a future producer pairing a high α with a clampable origin, not a current one. That is a weaker claim than the docstring made, and a more useful one, because it tells the next person what to check when they add a producer.


Your suggested test, plus its scope

f7121979. Asserting only the inequality would not have been enough — deflation 0.2 → 0.4 leaves the max at 3.6, still under 4.0, while the stated 1.8 silently becomes false. So the four shipped constants, the derived ceiling, and the margin are pinned separately.

Six mutants, all killed: deflation 0.2→0.4, requirement prior 9.0→25.0, threshold 4.0→1.5, deflated floor 0.5→5.0, unknown-type fallback factual→requirement, and a case-insensitive deflation gate.

Its docstring states what it does not cover, so it doesn't read as a stronger guarantee than it is: it bounds α by source, and constrains neither route_overrides nor migration-preserved rows on unknown.


One thing I did not fix

The two selectors still differ on the α arm — enumeration uses threshold_alpha, the re-check uses target_alpha — so the re-check is looser there. That is pre-existing and reads as deliberate (a row already at target shouldn't be re-clamped), but the "must agree exactly" comment overstates what is actually guaranteed. The new test asserts the eligibility predicate is shared, which is the accurate claim. Left as-is; say the word if you'd rather it were tightened.


State

Rebased onto current main — FF, 6 commits, all signed. Full suite green. Discretion grep on added lines clean.

Driving this to merge.

@robotrocketscience robotrocketscience removed the attn:unblock Needs answer from another session label Aug 6, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Collision, and one thing the branch was still missing — 58736df4

I picked this up as the author to answer the reviewer's two open items and found the
branch had moved: 88debcf7/f7121979/fe692c6c had already landed the same work.
Discarding none of it. I had built a ceiling test and a docstring correction of my
own; that branch's versions are better on every axis I compared — six killed mutants
against my two, source probes covering case and whitespace variants, and the
route-overrides producers analysed in both directions rather than named as a blanket
exception. Mine is dropped rather than merged.

One commit on top, because it closes a hole the attribution actually opens.

Why the consolidated rows have no trail

The attribution to _maybe_consolidate_content_hash_duplicates is right — I
reproduced the lattice independently and it is bit-exact, not approximate:

deflated factual prior  = 3.0 * 0.2 = 0.6000000000000001
sum([0.6000000000000001]*5) = 3.0000000000000004   beta 5.0   -> 185 rows
sum([0.6000000000000001]*4) = 2.4000000000000004   beta 4.0   ->  65 rows
sum([0.6000000000000001]*3) = 1.8000000000000003   beta 3.0   ->  10 rows

Every eligible row above 1.8 is an IEEE-754-identical k-fold sum with beta == float(k). Nothing else in the codebase moves α and β together.

But a reader who checks that attribution hits a contradiction and concludes it is
wrong: consolidation is not a trail-less writer. It inserts one synthetic
consolidation_migration corroboration per consumed duplicate, which would have
excluded every row it produced.

The resolution is that zero of them survive:

content_hash_dedup_complete   2026-04-29T01:38:58.670999+00:00
content_hash_unique_applied   2026-04-29T01:40:20.871559+00:00
MIN(belief_corroborations.ingested_at)  2026-04-29T01:40:55.727811+00:00
corroborations older than that:  0        (against 6,382 beliefs created before it)
consolidation_migration rows surviving:  0

_maybe_apply_content_hash_unique ran 82 seconds after the dedup marker and its
DROP TABLE beliefs cascaded belief_corroborations away wholesale — #336, since
fixed by PRAGMA foreign_keys=OFF around the swap.

This strengthens the section's conclusion rather than qualifying it. A
consolidation running today leaves its trail and yields no candidates, so that
population is residue of two migrations interacting and cannot regrow the same way.

Also added: name the store or don't quote the number

Same shipped selector, same default threshold, two stores on this machine:

store matched at α>4.0 max α
development 0 3.0000000000000004
another on this machine 1,310 105.00000000000001

Both figures observed directly, read-only. A bare "0 rows match, so this is safe"
is not a property of the code.

On the reviewer's two asks

Both are now answered, though not by me: the 1.8 is correct about
get_source_adjusted_prior
and is pinned by a test rather than by prose, and
unknown is named. Corroborating one figure the branch asserts — agent_inferred,
the origin the deflation argument actually governs, caps at exactly 1.8 across
11,243 selector-eligible rows
on the development store. The entire excess is
unknown.

Not fixed here, worth someone's attention

The 749 unlocked non-user rows above α=4.0 on the development store are excluded
solely by the NOT EXISTS feedback_history arm. Anything that ever prunes or ages
out feedback_history turns all 749 into matches at once. And the 1,310 rows above
α=100 in the table above sit on agent_inferred — the origin the ceiling argument
governs — so something reaches α=105 there that the insert-path bound does not
explain. I did not chase either; say the word and I will file them.

Verification

  • full suite: 7398 passed, 70 skipped, 71 xfailed (with the archive extra).
  • tests/test_clamp_ghosts.py: 27 passed.
  • every number above re-derived first-hand, read-only, not taken from the review.
  • discretion grep on added lines vs main: clean. All 7 commits signed.
  • pushed as a fast-forward on fe692c6c — no force, nothing overwritten.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

The premise in "Blocked on one non-required bot check" is false — option (1) does not exist

Not a disagreement about the merge risk. The workflow file contradicts the
mechanism the choice rests on, so the operator was choosing between an option
and a non-option.

The comment states:

aelf-ready.sh aborts on any failing check, which is stricter than the
merge-train, which verifies only the required set.

The merge-train does not verify only the required set. .github/workflows/merge-train.yml:170-177
pulls every check-run on the head SHA and filters out exactly two names — its
own jobs, to avoid a deadlock:

[.check_runs[] | select(.name != "Attempt merge-train FF" and .name != "merge")]
| group_by(.name) | map(max_by(.started_at))

and :188-190 fails on any of them:

fails=$(... select(.c == "failure" or .c == "timed_out" or .c == "action_required") ...)
if [ -n "${fails}" ]; then
    fail_and_unlabel "required check(s) failed: ..."
fi

grep -n 'required' merge-train.yml returns only comments, required_signatures
references, and that message string. The ruleset's five required contexts
(secrets-scan, pattern-scan, history-scan, pytest (3.12), pytest (3.13))
are never read by the workflow. The :168 comment about mirroring GitHub is
about #632's per-name latest-run dedup, not about a required-set filter.

Consequence for this PR: labelling ready-to-merge with Sourcery review
red will unlabel and refuse. aelf-ready.sh is not stricter than the train here
— on this specific point they agree, and the train is the one that will act on
it. So option (1), "merge past it, it is not a required context", has no
sanctioned path; it reduces to an out-of-band admin merge, which bypasses the FF
serialization the whole #602 design rests on.

The false-positive verification is independently confirmed and none of this
disputes it: origin_placeholders is built from len(USER_PRIOR_ORIGINS) so it
carries placeholders only, created_clause is a constant chosen by a truthiness
test, and every value reaches SQLite as a bound ?. Nothing injectable.

Operator ruling (2026-08-06 ~15:55Z): unblock this PR via option (2) —
hoist the two complete query strings into plain variables before
conn.execute(...) (opengrep matches concatenation at the execute call),
keeping the shared eligibility / created_clause fragments so the enumeration
and the write-lock re-check cannot drift into two selectors. The gating defect
itself is filed separately as #1397 so a CI-policy change does not ride on a
bugfix PR.

Also note, unrelated to the bot: this branch is 4 commits behind main
(git rev-list --left-right --count github/main...github/pr1394 -> 4 3), and
merge-train.yml:109 aborts if the branch is not FF. Rebase before labelling,
and label last — force-pushing under an existing label trips the head-moved
abort at :97-102.

Review claim is not mine; leaving the work with the holder.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Operator ruling — clear the Sourcery red with nosemgrep, then rebase

Ratified 2026-08-06. This PR is code-complete with all five required contexts
green; the only thing between it and the train is the Sourcery review check-run
failing on an opengrep sqlalchemy-execute-raw-query finding that two reviewers
independently verified as a false positive.

Disposition: add # nosemgrep: python.sqlalchemy.security.sqlalchemy-execute-raw-query
on the two conn.execute(...) calls in clamp_ghosts.py.
Two comment lines.
Code shape unchanged, the shared eligibility fragment (which the review
identified as load-bearing, since it keeps the enumeration and undercount queries
in sync) is preserved, and the finding stays visible at the line it fires on.
Prior art for the shape is the # nosec at pre_issue_create_hook.py:398.

Rejected, with reasons on the record:

  • Restructure the query — contorts working code to satisfy a rule that is
    wrong here, and costs the shared fragment.
  • Disable the rule org-wide — probably the right long-term answer given this
    codebase is 100% raw sqlite3 and there is no .sourcery.yaml to suppress it
    per-repo, but it is a standing security-scanner removal and gets its own
    decision rather than riding on this PR.
  • Admin-merge once — leaves the check red and sets no precedent worth having.

Sequencing matters here

merge-train.yml:188 computes failures over all check-runs on the head SHA,
not just the required set, so a red Sourcery strips ready-to-merge and bounces
the PR. And this branch is behind main, so it needs a rebase — which changes
the head SHA and re-triggers Sourcery on the same lines.

Commit the nosemgrep lines before the rebase-and-label sequence, or the
loop repeats: rebase → Sourcery re-fires → label stripped → rebase again.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Ruling applied — nosemgrep at the two execute sites (b80c6a68)

Both conn.execute calls in clamp_ghosts.py now carry
# nosemgrep: python.sqlalchemy.security.sqlalchemy-execute-raw-query on the
line directly above, with the reason inline. Preceding-line placement is what
the scanner honours; the pragma is the last comment line before the call so the
explanation above it does not push it out of range.

Clean: pytest -k clamp_ghost 27 passed / 3 skipped; discretion grep on added
lines empty; commit signed (G).

Two things the ruling could not have seen

1. The restructure it rejected had already landed. 88debcf7
(make the selector SQL static, bind origins as JSON) was pushed at 16:20Z,
36 minutes before the ruling. The rejected option — "restructure the query,
costs the shared fragment" — describes a change that was made without costing
the fragment: _ELIGIBILITY_SQL is still shared by _GHOST_SELECT_SQL and
_GHOST_RECHECK_SQL, and the origin set now travels as one bound JSON array
read by json_each rather than as generated placeholders. So the suppression
is belt-and-braces over an already-static query rather than the sole defence.
Keeping it: it is two comment lines, it documents the argument at the site, and
it costs nothing if the rule has stopped matching.

2. The red it targets cannot currently be reproduced. Sourcery review on
58736df4 is skipped, not failure:

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

That matters for the sequencing argument, not against it — a rate-limited skip
is not a pass, and the limit resets. Committing the suppression before the label
is still the right order.

Rebase step is a no-op

The ruling's second half assumed the branch was behind. It is not:
git merge-base --is-ancestor github/main HEAD succeeds and HEAD..github/main
is 0 commits. Proceeding straight to aelf-ready.sh once checks settle.

@robotrocketscience
robotrocketscience force-pushed the fix/issue-1374-hrr-lock-starvation-clamp-ghosts branch 2 times, most recently from 89521ea to f1f46c7 Compare August 6, 2026 23:39

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/aelfrice/clamp_ghosts.py (1)

359-363: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider validating the created_before format.

The cutoff reaches SQLite as an opaque string and is compared lexicographically against created_at. An operator who passes 2026-4-1 or 04/15/2026 gets a silent wrong result rather than an error: the first under-selects, the second matches every row whose created_at starts with a digit greater than 0. Under --apply that over-clamps.

A datetime.fromisoformat probe at this point converts the mistake into a ValueError, matching how the function already rejects bad α values.

♻️ Proposed refactor
     created_before = created_before or None
+    if created_before is not None:
+        try:
+            datetime.fromisoformat(created_before.replace("Z", "+00:00"))
+        except ValueError as exc:
+            raise ValueError(
+                f"created_before must be an ISO-8601 timestamp; "
+                f"got {created_before!r}"
+            ) from exc
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aelfrice/clamp_ghosts.py` around lines 359 - 363, Validate non-None
created_before with datetime.fromisoformat at the normalization point before it
reaches _ELIGIBILITY_SQL, allowing valid ISO timestamps while raising ValueError
for malformed formats. Preserve the existing falsy-to-None behavior so empty
values still mean no cutoff, and match the existing invalid-alpha error
behavior.
tests/test_clamp_ghosts.py (1)

523-533: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a negative-limit case to this test.

This test pins that limit=None reaches every match through the -1 sentinel. It leaves the adjacent case unpinned: an explicit negative limit from a caller also becomes uncapped, because int(limit) is forwarded verbatim. See the related comment on src/aelfrice/clamp_ghosts.py.

If you add the validation guard there, pin it here too.

💚 Proposed test
def test_negative_limit_is_rejected_not_treated_as_uncapped(
    store: MemoryStore,
) -> None:
    # A negative LIMIT is uncapped in SQLite, so a caller asking for a
    # cap must not silently get the whole store under --apply.
    for i in range(5):
        store.insert_belief(_mk(f"g{i}", alpha=9.0))

    with pytest.raises(ValueError, match="limit"):
        clamp_ghost_alphas(store, dry_run=False, limit=-1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_clamp_ghosts.py` around lines 523 - 533, Add a separate test next
to test_none_limit_processes_every_match that inserts matching beliefs and
asserts clamp_ghost_alphas with dry_run=False and an explicit negative limit
raises ValueError matching “limit”. Preserve the existing limit=None uncapped
test, and import pytest if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aelfrice/clamp_ghosts.py`:
- Around line 365-367: Reject negative limit values in the validation logic
alongside the existing threshold_alpha and target_alpha checks, while preserving
None as the no-limit sentinel. Also update the clamp-ghosts CLI --limit argument
to use the existing _positive_int validator so invalid values fail before
opening the store.
- Around line 39-42: Update the two paragraph openers in the explanatory text:
make the line 39 statement explicitly identify get_source_adjusted_prior as the
subject instead of using “It,” and make the line 114 statement explicitly refer
to migrate()’s inability to re-run rather than relying on the earlier claim.
Preserve the existing technical meaning and auditable justification.

---

Nitpick comments:
In `@src/aelfrice/clamp_ghosts.py`:
- Around line 359-363: Validate non-None created_before with
datetime.fromisoformat at the normalization point before it reaches
_ELIGIBILITY_SQL, allowing valid ISO timestamps while raising ValueError for
malformed formats. Preserve the existing falsy-to-None behavior so empty values
still mean no cutoff, and match the existing invalid-alpha error behavior.

In `@tests/test_clamp_ghosts.py`:
- Around line 523-533: Add a separate test next to
test_none_limit_processes_every_match that inserts matching beliefs and asserts
clamp_ghost_alphas with dry_run=False and an explicit negative limit raises
ValueError matching “limit”. Preserve the existing limit=None uncapped test, and
import pytest if needed.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2c6c820b-e9b3-4c43-83e1-bbb150906eaa

📥 Commits

Reviewing files that changed from the base of the PR and between 7d48c95 and f1f46c7.

📒 Files selected for processing (5)
  • src/aelfrice/clamp_ghosts.py
  • src/aelfrice/cli.py
  • src/aelfrice/retrieval.py
  • tests/test_clamp_ghosts.py
  • tests/test_retrieve_v2_hrr_structural.py

Comment thread src/aelfrice/clamp_ghosts.py Outdated
Comment thread src/aelfrice/clamp_ghosts.py
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Both review threads taken — one was a real data-integrity defect, and it predates this PR

51477498 and 1c6e12cd.

The negative --limit finding is correct, and it is worse than "minor"

params.append(_NO_LIMIT if limit is None else int(limit)) forwards the caller's
value straight to LIMIT, and SQLite reads a negative LIMIT as unbounded. So
--limit -1 did not cap at one row — it processed every matching row, on the
path that mutates under --apply. Measured on a five-row fixture with the guard
removed:

matched= 5 clamped= 5
alphas: [4.0, 4.0, 4.0, 4.0, 4.0]

The cap silently inverted into "no cap". --limit is type=int with no lower
bound at cli.py:9605, so an operator typo is enough to reach it.

This is pre-existing, not introduced here. On github/main the clause is
appended conditionally — if limit is not None: query += " LIMIT ?" — and passes
the same unvalidated value, so the inversion is reachable there too. Making the
SQL static did not cause it and did not mask it; the review found it because the
value became visible at one site. Fixed rather than deferred: it is four lines,
it is in this file, and it mutates rows.

The guard rejects < 0 rather than falsy values, because limit=0 means what it
says — LIMIT 0 selects nothing — and stays legal. A second test pins that
boundary, so a later "simplification" to if not limit turns it red.
_cmd_clamp_ghosts already maps ValueError to exit 2, so the CLI surfaces it
with no further wiring.

Mutation-checked, __pycache__ cleared either side of the revert and the file
restored by copy rather than git checkout:

guard removed  -> FAILED test_validation_rejects_negative_limit (DID NOT RAISE)
                  test_zero_limit_selects_nothing passes (correct — it is a boundary pin)
guard restored -> 29 passed

The dangling antecedents: both, not one

The review named the opener at :39; there was a second one. "It bounds α by
source" followed a paragraph ending on a test name, so the pronoun had no
referent — the subject is get_source_adjusted_prior. And `migrate()` can.
answered a claim two paragraphs above it (that the population cannot regrow),
with the headroom paragraph wedged in between. Both now carry their own subject.

No behaviour change in that commit; it is docstring prose only.

`_route_structural_query` charged locks against the whole token budget
with no floor, re-introducing #1014 on a lane that is default-ON and
returns early: on a lock-saturated store a structural query returned
the locks and nothing else. It now measures lock cost with
`lock_injection_tokens` (so a reference-tier lock is charged at its
manifest line when the caller renders it that way) and caps the HRR
tail at `RELEVANCE_BUDGET_FLOOR_FRACTION` of the budget, the same
mechanisms the textual path already uses. `manifest_reference_locks`
is threaded down from `retrieve_v2`. Byte-identical packing whenever
the locks already leave at least the floor. Refs #1374.
The selector keyed only on lock_level, alpha and an empty audit trail,
which a freshly-ingested user belief satisfies by construction: the
insert path writes the undeflated TYPE_PRIORS alpha (9.0 for a
requirement) onto a new row that has no feedback and no corroboration
yet, so the tool clamped it and wrote an audit row attributing the
clamp to itself. Rows whose origin is user_stated, user_corrected,
user_validated or user_transcript are now excluded, and an optional
--created-before cutoff lets an operator confine a one-shot clamp to
rows predating the migration. Both predicates apply to the enumeration
query and the under-write-lock re-check. Refs #1374.
…path

The docstring stated a universal: non-user origins are deflated at
insert, so no legitimate insert on them can clear the threshold. That
is true of the deterministic classifier path — max reachable alpha
there is 1.8 — and not true of the LLM-router path, which bypasses
get_source_adjusted_prior and writes the router's (origin, alpha)
verbatim. A route with origin=agent_inferred, alpha=9.0 and no
audit_source matches every arm of the selector.

Not widening the exclusion: an arbitrary router-assigned alpha with no
audit trail is the class this tool exists to clamp, and excluding
agent_inferred wholesale would gut it. The exception is named instead,
because this file's value is that its selector's justification is
auditable — the guarantee is "no deterministically derived belief is a
false positive", not "no belief is".

Refs #1374.
… JSON

The selector was composed per call: an `IN (?, ?, …)` placeholder run
sized from len(USER_PRIOR_ORIGINS), plus a conditionally-appended
created_at clause and a conditionally-appended LIMIT. No caller value
ever reached the SQL text, but the shape is the one opengrep's
sqlalchemy-execute-raw-query rule matches, and it kept the `Sourcery
review` check red — which the merge train blocks on.

Both queries are now module constants with no interpolation. The origin
exclusion arrives as one bound JSON array read by `json_each`, the same
mechanism store.list_stale_speculative_ids adopted for the same reason
in #1171; the cutoff is a bound `? IS NULL` disjunction; LIMIT is always
bound, using SQLite's negative-means-unbounded reading for the no-cap
case. There is no placeholder count left to keep in sync with a
parameter count.

Behaviour is unchanged. `created_before` is normalised falsy-to-None
first, so the empty string keeps meaning "no cutoff" rather than
"created before the empty string", which under a bound null-check would
match nothing.

Sharing the predicate between the enumeration query and the
under-the-write-lock re-check was already deliberate and is now
structural: one module constant, spliced by one parameter helper, with
tests asserting both queries contain it. Four tests cover what the
existing suite did not distinguish — the empty-string cutoff, the
no-cap LIMIT sentinel, the sorted serialisation (monkeypatched, since
the shipped tuple is already alphabetical and cannot exercise it), and
the shipped origin list written out literally.

Refs #1374.
…hold

The selector's safety argument was prose: non-user origins deflate to at
most 1.8, the threshold is 4.0, therefore no legitimate insert on a
clampable origin is selectable. Nothing failed when a prior moved.

Now executable. Asserting only the inequality would not be enough — a
deflation factor of 0.4 leaves the maximum at 3.6, still under 4.0,
while the stated 1.8 quietly becomes false. So the four shipped
constants, the derived ceiling, and the inequality are pinned
separately, and the margin is named.

Sweeps every type the classifier can emit plus an unmapped string, so
the unknown-type fallback is covered; repointing that fallback from
factual to requirement raises the real ceiling and is caught. Source
labels include case and whitespace variants, since the deflation gate is
an exact comparison against "user".

Six mutants, all killed: deflation 0.2->0.4, requirement prior 9.0->25.0,
threshold 4.0->1.5, deflated floor 0.5->5.0, fallback prior
factual->requirement, and a case-insensitive deflation gate.

The docstring states the scope so this does not read as a stronger
guarantee than it is: it bounds alpha by source, and constrains neither
route_overrides nor migration-preserved rows on origin='unknown'.

Refs #1374.
…nknown'

Review found 260 rows on the development store satisfying every arm of
the selector but the threshold, at alpha up to 3.0 — against a docstring
claiming 1.8 was the maximum reachable. Three corrections, no selector
change; the tool still matches 0 rows there.

The 1.8 bound is right, and is about the INSERT path. It is now pinned
by a test rather than by this paragraph. What the text failed to say is
that it bounds alpha by source while the selector excludes by origin.

The stated LLM-router exception is wrong in both directions. The
mechanism is real — derive() writes route_overrides' (origin, alpha)
verbatim — but neither shipped producer reaches a clampable origin with
an inflated alpha, and they miss it for different reasons.
llm_classifier is restricted to {agent_inferred, document_recent} and
takes its alpha from get_source_adjusted_prior on the candidate's
doc:/ast:/git: label, so it deflates to 1.8, not 9.0.
claude_memory_reconcile does write the undeflated prior up to 9.0, but
on origin=user_validated, which USER_PRIOR_ORIGINS excludes. The
exposure is a future producer, not a current one.

origin='unknown' is the actual gap and is now named. It is the one
clampable origin carrying alpha above 1.8 in practice: migrate() copies
legacy alpha verbatim, stamps 'unknown' on unlocked non-correction rows,
and copies neither feedback_history nor belief_corroborations, so an
earned alpha arrives indistinguishable from a fabricated ghost. Left
clampable deliberately — it is the target population, not a bystander,
and excluding it would make the tool a no-op; --created-before is the
mitigation.

Also corrects the empirical attribution. The live population above 1.8
sits on an exact k*(0.6, 1.0) lattice — k copies of the deflated factual
prior summed — which is _maybe_consolidate_content_hash_duplicates
(#219), not _read_legacy_beliefs, which copies alpha through unchanged
and cannot produce that shape. That consolidation is marker-gated and
cannot re-run, but migrate() can: it is reachable via 'aelf migrate
--apply' and 'aelf doctor', so "one-shot" is a property of a given
store's existing rows, not of the tool. Re-run after any migration.

Refs #1374.
The attribution to _maybe_consolidate_content_hash_duplicates is right,
and it raises a question the paragraph left open: consolidation is not
a trail-less writer. It inserts one synthetic consolidation_migration
corroboration per consumed duplicate, which would have excluded every
row it produced. A reader checking the claim finds that insert and
concludes the attribution is wrong.

It is not. Zero of those corroborations survive, because
_maybe_apply_content_hash_unique ran 82 seconds after the dedup marker
and its DROP TABLE beliefs cascaded belief_corroborations away
wholesale — #336, since fixed by PRAGMA foreign_keys=OFF around the
swap. Verified on the development store: dedup marker 01:38:58Z, unique
marker 01:40:20Z, earliest surviving corroboration 01:40:55Z against
6,382 beliefs created before it.

That strengthens the section's own conclusion rather than qualifying
it. A consolidation running today leaves its trail and yields no
candidates, so the existing population is residue of two migrations
interacting and cannot regrow the same way.

Also warns against quoting a headroom figure without its store: the
shipped selector matches 0 rows at alpha>4.0 here and 1,310 rows above
alpha=100 on another store on this machine. Same code, both.

Refs #1374.
…ts two sites

The scanner's sqlalchemy-execute-raw-query rule fires on the concatenation
that assembles the ghost selector and its under-write-lock recheck. Both
query strings are module constants built from string literals alone, and
every variable part — threshold, cutoff, origin set, limit — is a bound
parameter, so no untrusted input can reach the SQL text.

Suppress at the two execute sites rather than restructuring: the shared
_ELIGIBILITY_SQL fragment is what keeps the enumeration and recheck queries
in sync, and the finding stays visible on the line it fires on.
… no cap

SQLite treats a negative LIMIT as unbounded, so `--limit -1` processed
every matching row rather than one — the inverse of the cap the caller
asked for, on the path that mutates under --apply. Measured on a
five-row fixture: matched=5 clamped=5, every alpha driven to the target.

The guard rejects `< 0` rather than falsy values, because `limit=0`
means what it says (LIMIT 0 selects nothing) and stays legal; a second
test pins that boundary. `_cmd_clamp_ghosts` already maps ValueError to
exit 2, so the CLI surfaces it without further wiring.

Pre-existing: the same inversion is reachable on main, where the LIMIT
clause was appended conditionally. Reported by review on this branch.
"It bounds alpha by source" opened a paragraph whose predecessor ended
on a test name, so the pronoun had no referent; the subject is
get_source_adjusted_prior. "migrate() can." answered a claim two
paragraphs above it about the population not regrowing, with the
headroom paragraph in between. Both now carry their own subject.
@robotrocketscience
robotrocketscience force-pushed the fix/issue-1374-hrr-lock-starvation-clamp-ghosts branch from 1c6e12c to 1ab5506 Compare August 7, 2026 00:12
@robotrocketscience robotrocketscience added the ready-to-merge Trigger merge-train: FF main to this PR's head label Aug 7, 2026
@github-actions
github-actions Bot merged commit 1ab5506 into main Aug 7, 2026
30 checks passed
@github-actions github-actions Bot removed the ready-to-merge Trigger merge-train: FF main to this PR's head label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

merge-train: merged 1ab5506main via FF push.

robotrocketscience added a commit that referenced this pull request Aug 9, 2026
…k-run

The wait loop enumerated every check-run on the head SHA and unlabelled
on any failure, so Sourcery, CodeRabbit and every other advisory bot were
de facto merge-blocking — while the message said 'required check(s)
failed', naming a set the workflow never read. PR #1394 sat blocked
behind a verified-false SQL-injection finding.

The required contexts are now resolved at run time from
rules/branches/main, which needs only read access, so the workflow cannot
drift from the ruleset. pending is scoped to the same set, so a slow or
silent advisory bot no longer holds the train to CHECK_TIMEOUT_SECONDS.
A failing advisory check is still reported, labelled as not gating.

Fail-closed: resolving zero required contexts aborts rather than merging.
An empty set is indistinguishable from a moved ruleset or a token that
lost read access, and reading it as 'nothing is required' would be worse
than the over-blocking this replaces.

The decision moved out of inline jq into scripts/merge_train_gate.py
because a gate that cannot be tested is how this survived. #632's
per-name latest-run dedup and its cancelled-is-not-failure rule are
carried over and pinned, both directions.

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

Labels

author-Gylf PR coordination mutex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(retrieval): HRR lane lock-starvation and clamp_ghosts false positives (#1158 §1/§12)

1 participant