Skip to content

feat(detectors): pin and version the thresholds behind the 2.8% non-spine edges - #1362

Merged
github-actions[bot] merged 15 commits into
mainfrom
feat/issue-1355-detector-threshold-manifest
Aug 6, 2026
Merged

feat(detectors): pin and version the thresholds behind the 2.8% non-spine edges#1362
github-actions[bot] merged 15 commits into
mainfrom
feat/issue-1355-detector-threshold-manifest

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Closes #1355. Parent: #1157, carved from #1283 on the operator ruling of 2026-08-05.

What this is

The funded half of #1283 AC2 that was never started: a frozen, versioned record of the constants that decide the non-spine edge set, so "edges are recomputable" is a checkable claim for the 2.8% rather than an assertion.

aelfrice.detector_thresholds pins 22 constants across 9 modules behind DETECTOR_THRESHOLDS_VERSION = 1.

The defect being fixed could not fail

The issue calls the existing coverage tautological. It is, and this PR measures it rather than restating it:

mutation: DEFAULT_JACCARD_MIN 0.4 -> 0.9 in source
  tests/test_relationship_detector.py::test_config_loader_overrides_and_falls_back : PASSED
  tests/test_detector_thresholds_manifest_1355.py                                  : 1 failed

assert cfg.jaccard_min == DEFAULT_JACCARD_MIN compares the constant to itself, so it survives any change to what the symbol resolves to. Same class as the #1353 synth-exclusion pin.

Design — why the manifest imports nothing

The manifest holds hand-written literals and imports nothing from aelfrice. If it imported the constants it describes, it would reproduce the exact defect it exists to close. The test does the importing and re-derives each pinned form from the live object; the asymmetry is the mechanism.

  • Scalars pin as literals so a reviewer can check 0.4 against the source line by eye. A separate arm rejects "repairing" a red scalar by converting it to a digest, which would otherwise satisfy arm 1 while destroying the reviewability the issue asked for.
  • Collections pin as a digest of a canonical form that includes regex flags — dropping re.IGNORECASE from the triple patterns changes which triples match without changing one character of pattern text, and it goes red.
  • The digest is keyed BY version (DIGEST_HISTORY[version], cfe1054d). An earlier revision folded the version into the digested content, which let a value change be absorbed by editing the digest literal with the version untouched — two behaviours shipping as version 1. Keyed by version, the ways back to green are to revert or to bump-and-append.

Coverage is swept, not hand-listed

The third checkbox is the easy one to under-deliver: pinning relationship_detector alone would look complete. Arm 3 extracts every insert_edge call site from source text and requires each module to be either pinned or excluded with a stated reason — 4 covered, 6 excluded (the spine, the two DERIVED_FROM paths, the cross-store copier, two synthetic-fixture builders). A companion arm asserts only the store writes the edges table directly, so the sweep cannot be evaded with raw SQL.

Two writers surfaced this way that a relationship_detector-shaped reading would have missed:

  • contradiction.CLASS_NAMES is keyed on the PRECEDENCE_* integers, and _pick_winner compares those to choose the winner — which becomes the edge's src. Reordering them does not resize the edge set, it reverses edges already in it. No count-based check would notice.
  • wonder.lifecycle._CONSTITUENT_KEY_VERSION is the phantom idempotency-key prefix. Bumping it makes every existing phantom miss its own dedup guard and re-ingest, minting a second full set of RELATES_TO edges.

Verified by mutation

The issue asks for this explicitly. Eight cases, each restored and re-baselined between runs:

mutation manifest tests note
DEFAULT_JACCARD_MIN 0.4 → 0.9 (source) 1 failed pre-existing test stays green
add a token to _NEGATION_TOKENS (source) 2 failed _STOPWORDS is a union of it — coupling documented
drop re.IGNORECASE from a triple pattern 1 failed pattern text unchanged
widen the bm25 token pattern to [\w-]+ 1 failed the upstream supplier arm
ANCHOR_TEXT_MAX_LEN 1000 → 500 1 failed changes persisted rows, not edge existence
version bump alone, no appended row 2 failed manifest_digest_is_pinned + digest_history_is_contiguous_and_complete
relax a pinned value to match a drift 2 failed can't quietly re-baseline
comment-only edit to the manifest 32 passed digest is over data, not source text

Reachability was checked, not assumed

Two entries in a first pass would have shipped gates text that was false, and finding that is most of the value here.

  • The wonder bake-off is not the writer. A first pass pinned six wonder.{evaluator,strategies} constants as the gate on RELATES_TO. wonder/__init__.py says outright that those strategies are research-only and do not write to a live store, and their sole importer (wonder/runner.py) builds against MemoryStore(":memory:"). They decide no edge in any user's store, so they are dropped rather than shipped as padding. The paths that do decide it are pinned instead: BFS hops ranked by wonder_consolidation.score then sliced by --top, and the dispatch-seeded persist-docs path.
  • value_compare is dormant, and now says so. No shipped caller passes use_value_comparison=True — it defaults False and relationships_audit does not thread it — so the slot gate mints nothing today. The four entries are kept and relabelled: pinning now makes flipping that flag a one-line change against a known baseline rather than a change to four unpinned constants at once.
  • Suppliers are invisible to the sweep. Arm 3 only sees modules that call insert_edge, so a module that supplies the decision is structurally undetectable. Three were added by hand — bm25._TOKEN_PATTERN (the token universe for every Jaccard and membership test in the detector, so it outranks any single cutoff), models.ANCHOR_TEXT_MAX_LEN, wonder_consolidation._TOKENIZER_DROP. The two left unpinned (dedup's prefilter semantics, config_discovery's file resolution) are behavioural surfaces rather than constants and are named in the module docstring so their absence is a recorded decision.

Covered-ness now matches on the exact module, not a package prefix — prefix matching is precisely what let wonder.lifecycle look covered by wonder.evaluator.

Limits, stated rather than papered over

  • Overrides. Three entries are defaults a .aelfrice.toml [relationship_detector] section can override, so the manifest pins the shipped default, not what a given store ran with. Every entry names its override mechanism so the two are distinguishable.
  • Forward-only. The edges table has no version and no created_at, so this does not make a historical edge attributable to the thresholds that produced it. Adding those columns is the edges-table migration that left stores unopenable-forever in [Umbrella] Deployment and operational hardening #1161. Historical reproduction stays explicitly out of scope, per the issue.
  • Unnamed literals are out of reach. Entries resolve by (module, name). Three writers stamp weight=1.0 inline in the Edge(...) constructor; contradiction is the one that names it (SUPERSEDES_WEIGHT), which is why it is pinned and the others are not. Naming those three is a behaviour-preserving refactor that would close the gap, deliberately not bundled here. Transitively-reached constants are covered — _QUANTIFIER_TOKENS derives from QUANT_AXIS, and the noun-phrase fragments compile into _PATTERNS.

Verification

  • pytest: 7263 passed, 70 skipped, 71 xfailed. New file: 32 passed.
  • pyright (strict, covers src/aelfrice and tests): 0 errors on both new files.
  • Discretion grep on added lines vs github/main: clean.
  • No production behaviour changes — the manifest is a record and the tests read it. No constant was edited.

Summary by Sourcery

Pin and version detector threshold constants that determine non-spine edges and enforce coverage via a manifest-backed test suite.

New Features:

  • Introduce a frozen, versioned detector threshold manifest capturing 22 constants across key edge-writing and supplier modules, guarded by a digest and version number.

Bug Fixes:

  • Replace a tautological relationship-detector config test with manifest-based checks that can detect drift in detector thresholds.

Enhancements:

  • Add canonicalisation and hashing utilities to derive stable digests for complex threshold structures, including regex flags and dataclasses.
  • Enforce that all edge-writing modules are either explicitly covered by the manifest or excluded with a documented reason, and assert that only the store may write the edges table directly.
  • Document the new threshold pinning behaviour and its limits in the v4 changelog.

Tests:

  • Add tests ensuring pinned thresholds match live source, the manifest digest tracks version changes, and coverage over edge writers cannot silently regress.

Summary by CodeRabbit

  • Documentation

    • Added documentation for versioned detector-threshold manifests, including supported overrides, exclusions, and forward-only versioning limitations.
  • Reliability

    • Added safeguards to verify threshold consistency, manifest integrity, and coverage of edge-producing pathways.
    • Added validation for scalar and collection threshold representations.
  • Maintenance

    • Established a versioned record of detector settings to support traceability and prevent unintended behavioral changes.

@robotrocketscience robotrocketscience added the author-Setr PR coordination mutex label Aug 5, 2026
@sourcery-ai

sourcery-ai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a new detector thresholds manifest module that pins and versions constants affecting non-spine edges, plus tests that verify pinned values, enforce version/digest coupling, and ensure all non-spine edge writers are either covered or explicitly excluded, with a brief changelog note documenting the behavior.

File-Level Changes

Change Details Files
Introduce a frozen, versioned manifest of detector thresholds and supporting helpers for canonicalisation and digesting.
  • Add aelfrice.detector_thresholds module that defines DETECTOR_THRESHOLDS_VERSION, pin_value, size_of, manifest_digest, and MANIFEST_DIGEST.
  • Define PinnedThreshold dataclass and THRESHOLDS tuple pinning 22 constants across multiple detector-related modules with metadata (kind, edge types, overridability, gates text).
  • Add helpers for canonicalising complex values (regexes, dataclasses, sets, dicts) into stable JSON-serialisable forms and computing SHA-256 digests.
  • Record COVERED_WRITER_MODULES and EXCLUDED_WRITERS to document which modules writing non-spine edges are pinned vs intentionally excluded.
src/aelfrice/detector_thresholds.py
Add tests that verify manifest correctness against live source, enforce version/digest discipline, and sweep for full writer coverage.
  • Add parametrized tests that import each pinned constant, recompute its pinned form using pin_value, and assert both value and collection size match the manifest.
  • Add tests that ensure scalar thresholds are pinned as literals (not digests) and that entries are unique, well-formed, and use known kinds.
  • Add tests that assert manifest_digest equals MANIFEST_DIGEST and that DETECTOR_THRESHOLDS_VERSION is a positive int, coupling content changes to version bumps.
  • Implement a source sweep for insert_edge call sites to ensure all non-spine edge writers are either listed in COVERED_WRITER_MODULES or EXCLUDED_WRITERS, and assert only aelfrice.store writes edges via raw SQL.
tests/test_detector_thresholds_manifest_1355.py
Document the new detector thresholds pinning and limitations in the changelog.
  • Extend v4 changelog with a detailed note describing pinned thresholds, coverage approach, reachability decisions, and explicit limits (overrides, forward-only reproduction).
CHANGELOG/v4.md

Assessment against linked issues

Issue Objective Addressed Explanation
#1355 Introduce a frozen, versioned manifest enumerating the detector constants (thresholds) that determine the non-TEMPORAL_NEXT, non-DERIVED_FROM edge set, guarded by a DETECTOR_THRESHOLDS_VERSION.
#1355 Add tests that assert the literal shipped values of these detector thresholds (not just symbols), such that changing any pinned value without coordination causes tests to fail, including a digest guard that ties manifest content to DETECTOR_THRESHOLDS_VERSION.
#1355 Ensure the manifest’s coverage spans every writer of non-TEMPORAL_NEXT, non-DERIVED_FROM edges (not only relationship_detector), with a sweep over insert_edge call sites so that each writer is either pinned or explicitly excluded with a stated reason.

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

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: 52 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: 8ebe4bbb-e860-4cc4-954d-04cb43b7d95a

📥 Commits

Reviewing files that changed from the base of the PR and between 146d743 and ec25ee6.

📒 Files selected for processing (3)
  • CHANGELOG/v4.md
  • src/aelfrice/detector_thresholds.py
  • tests/test_detector_thresholds_manifest_1355.py
📝 Walkthrough

Walkthrough

The PR adds a versioned detector-threshold manifest, canonical digest tracking, writer coverage metadata, and tests that detect threshold drift, invalid manifest changes, and unclassified edge writers.

Changes

Detector threshold reproducibility

Layer / File(s) Summary
Threshold contract and canonicalization
src/aelfrice/detector_thresholds.py
Defines manifest constants, canonical serialization and sizing helpers, and the frozen PinnedThreshold record.
Manifest, digest history, and writer coverage
src/aelfrice/detector_thresholds.py
Records detector thresholds, versioned manifest digests, covered writer modules, documented exclusions, and public exports.
Manifest validation and changelog
tests/test_detector_thresholds_manifest_1355.py, CHANGELOG/v4.md
Tests literal values, manifest structure, digest continuity, writer classification, and direct edges table writes. Documents the unreleased change and its limitations.

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

Possibly related PRs

  • robotrocketscience/aelfrice#444: Covers relationship-detector thresholds and edge-writing behavior, including manifest coverage for its POTENTIALLY_STALE writer.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR fulfills #1355 by adding a versioned literal manifest, mutation checks, writer coverage, and forward-only enforcement.
Out of Scope Changes check ✅ Passed The changes stay within scope and add only the manifest, enforcement tests, and related changelog documentation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly and concisely describes pinning and versioning detector thresholds that affect non-spine edges.
Description check ✅ Passed The description explains the purpose, implementation, verification, linked issue, scope, limitations, and test coverage in sufficient detail.
✨ 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 feat/issue-1355-detector-threshold-manifest

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:

  • 1110 changed lines (limit: 200)
  • 3 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, and left some high level feedback:

  • In test_manifest_digest_is_pinned, consider including the current manifest_digest() value in the assertion failure message (or a small helper/CLI to print it) so that updating MANIFEST_DIGEST is less error-prone and doesn’t require recomputing the digest out-of-band.
  • In test_scalar_entries_pin_a_literal_not_a_digest, the kind strings ("numeric_cutoff", "cap", etc.) are duplicated instead of using the KIND_* constants; reusing the existing constants would reduce the chance of typos or drift between the manifest and its validation.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `test_manifest_digest_is_pinned`, consider including the current `manifest_digest()` value in the assertion failure message (or a small helper/CLI to print it) so that updating `MANIFEST_DIGEST` is less error-prone and doesn’t require recomputing the digest out-of-band.
- In `test_scalar_entries_pin_a_literal_not_a_digest`, the kind strings (`"numeric_cutoff"`, `"cap"`, etc.) are duplicated instead of using the `KIND_*` constants; reusing the existing constants would reduce the chance of typos or drift between the manifest and its validation.

## Individual Comments

### Comment 1
<location path="src/aelfrice/detector_thresholds.py" line_range="152-162" />
<code_context>
+    return json.dumps(obj, sort_keys=True, separators=(",", ":"))
+
+
+def pin_value(obj: Any) -> str:
+    """Return the pinned form of a live constant.
+
+    Scalars pin as their literal, so the manifest stays readable and a
+    reviewer can check ``0.4`` against the source line by eye. Collections
+    pin as a digest of their canonical form — a stopword set is not
+    reviewable inline, but its digest changes the moment a token is added
+    or removed, which is the property the manifest needs.
+    """
+    if isinstance(obj, bool) or not isinstance(obj, (int, float, str)):
+        return "sha256:" + hashlib.sha256(
+            _dumps(_canonical(obj)).encode("utf-8")
</code_context>
<issue_to_address>
**suggestion:** Clarify or align the special handling of booleans in `pin_value` with the docstring.

The current implementation treats booleans as non-scalars and hashes them, which conflicts with the docstring’s claim that scalars pin as literals. If this is intentional (e.g. to distinguish True/False from 1/0), please either update the docstring to document the special-case behaviour for booleans, or adjust the condition so booleans are treated like other numeric scalars. Otherwise, the behaviour is surprising for anyone relying on the docstring.

```suggestion
    Scalars (including booleans) pin as their literal, so the manifest
    stays readable and a reviewer can check ``0.4`` or ``True`` against
    the source line by eye. Collections pin as a digest of their canonical
    form — a stopword set is not reviewable inline, but its digest changes
    the moment a token is added or removed, which is the property the
    manifest needs.
    """
    if not isinstance(obj, (int, float, str)):
        return "sha256:" + hashlib.sha256(
            _dumps(_canonical(obj)).encode("utf-8")
        ).hexdigest()
    return _dumps(obj)
```
</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/detector_thresholds.py
Comment thread src/aelfrice/detector_thresholds.py Fixed
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Garsecg:2026-08-05T23:42:02Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Idnn:2026-08-05T23:42:59Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Idnn:2026-08-05T23:43:04Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Idnn:2026-08-05T23:58:50Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Idnn:2026-08-05T23:58:55Z]

@robotrocketscience
robotrocketscience force-pushed the feat/issue-1355-detector-threshold-manifest branch from 1a13c31 to 0c86989 Compare August 6, 2026 00:09
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Review — the mechanism is sound, five of the 22 gates strings are not, and the central enforcement claim is false

The design call this PR is built on is right, and worth saying first: the manifest importing nothing from aelfrice while the test does the importing is what makes the comparison non-tautological, and it is the thing the issue actually asked for. Dropping the six wonder.{evaluator,strategies} constants after checking reachability was also the right call, and it is corroborated — wonder/runner.py builds against MemoryStore(":memory:").

I pushed five commits for the parts I could fix correctly. The rest needs you, and one of them is the PR's headline claim.

Every finding below was reproduced against live source. Where I ran the suite under mutation I cleared __pycache__ first — the two digests are the same length, so a same-second rewrite is otherwise served from a stale .pyc and the run reports the opposite of the truth. That bit me once on this PR; flagging it because several rows in your mutation table are same-length edits.


1. test_manifest_digest_is_pinned does not close the failure mode it is documented to close

This is the one to fix before merge, because it is the sentence the module is named after.

The digest contains the version, so bumping the version moves the digest. The claim needs the converse — that moving a value forces a version bump — and that never follows. MANIFEST_DIGEST is a hand-written literal beside the thing it digests, so the repair for a red digest is to update that literal. Which is exactly what arm 2's own failure message instructs: "manifest content or version changed without updating MANIFEST_DIGEST."

Demonstrated end to end, caches cleared:

DEFAULT_JACCARD_MIN: Final[float] = 0.4   ->   0.9      # live source
    value="0.4"                           ->   "0.9"    # manifest entry
    MANIFEST_DIGEST 1edce3ea…             ->   9f2403a8…
    DETECTOR_THRESHOLDS_VERSION           ->   1        # untouched

$ uv run pytest tests/test_detector_thresholds_manifest_1355.py -q
31 passed

A 0.4 candidate floor and a 0.9 one — most of the CONTRADICTS/POTENTIALLY_STALE population — both now ship as version 1. That is verbatim failure mode 2 in your own test-file docstring: "Someone updates the manifest to match their new constant and ships, leaving DETECTOR_THRESHOLDS_VERSION at 1 — so two different edge-producing behaviours claim the same version."

The mutation-table row relax a pinned value to match a drift | 2 failed | can't quietly re-baseline holds only because that run stopped one edit short of the repair the failure message asks for.

Four sites state this falsely and one of them ships to users: detector_thresholds.py:32-34, :87-88 ("makes this mechanical"), :611-612 ("which is what forces DETECTOR_THRESHOLDS_VERSION to move with it"), and CHANGELOG/v4.md:13. Your test docstring at tests/…:175-179 already states the honest version — a signal a reviewer reads — which contradicts the "mechanical rather than a convention" sentence two lines above it.

Two ways out, your call. Issue #1355's second criterion says "forces", so I lean to the first:

  • Make it true (~6 lines). Replace the bare constant with a frozen history — DIGEST_HISTORY: Final[dict[int, str]] = {1: "1edce3ea…"} — and assert manifest_digest() == DIGEST_HISTORY[DETECTOR_THRESHOLDS_VERSION]. Reusing version 1 with any other digest then goes red and stays red; bumping to 2 requires appending a row. Overwriting a historical row is still possible, but it is a visibly dishonest edit rather than the routine one.
  • Make the wording true. Strike the enforcement claim from all four sites and use the phrasing your test docstring already has.

Only truly mechanical enforcement is a CI check against the merge-base — if the THRESHOLDS digest differs from main's, require VERSION to have increased. That is a bigger change and I would not bundle it.

2. Five of the 22 gates strings are false or overstated

gates is the manifest's substance — "a constant that cannot answer this does not belong here" — and nothing tests these strings. Note they are inside the digest (_canonical walks every dataclass field), so fixing them moves MANIFEST_DIGEST; the version stays 1 since no pinned value changes and the module has not shipped.

contradiction.SUPERSEDES_WEIGHT (:363-367) — "does change the propagation arithmetic a recompute must reproduce byte-for-byte". There is no propagation arithmetic. Edge.weight is read in exactly three places: a BFS sort key (bfs_multihop.py:172,254,259), a floor comparison (clustering.py:145 against DEFAULT_CLUSTER_EDGE_FLOOR = 0.4), and persistence (store.py:6366,6383). The real effect is worth stating because it is sharper: at 1.0 the weight clears the clustering floor, and any value below 0.4 silently drops every SUPERSEDES edge out of candidate clustering. Keep the entry, rewrite the claim.

wonder.dispatch.UNCERTAINTY_THRESHOLD (:533-538) — "floor for a belief to become a wonder anchor … those anchors are the constituent tuple skill_integration persists". The anchor tuple is unfiltered:

dispatch.py:258   known_beliefs=tuple(candidates)          # no threshold applied
dispatch.py:441   anchors = tuple(b.id for b in ga.known_beliefs)
dispatch.py:446   speculative_anchor_ids=anchors

The threshold only builds high_uncertainty_beliefs (:237), which at :368 decides whether an uncertainty_deep_dive axis is emitted. That is a real path to RELATES_TO and the entry earns its place — but not by the stated mechanism.

relationship_detector.QUANT_AXIS (:305-309) — "The score is half the axis distance" is off by 2x:

q_term = abs(sa.quantifier_axis - sb.quantifier_axis) / 2.0   # :318
score  = (n_term + q_term) / 2.0                              # :322

A pure quantifier disagreement scores Δ/4. "always use uv" vs "sometimes use uv" is Δ=1.0 → 0.25, not 0.5 — so it lands as POTENTIALLY_STALE, on the far side of the very split the DEFAULT_CONFIDENCE_MIN entry says the manifest exists to record. A reviewer sizing an edit against confidence_min with this prose picks the wrong value.

relationship_detector.DEFAULT_JACCARD_MIN (:250-254) — "Lowering it enlarges the candidate pool and can only add edges; raising it can only remove them." The candidate pool is monotonic; the written edge set is not, because max_edges_per_belief (the entry two rows below) is consumed in sorted pair order, so a newly-admitted pair can evict one that was previously written.

triple_extractor._PATTERNS (:394-397)edge_types omits TEMPORAL_NEXT, and 4 of the 25 pinned patterns write exactly that type:

IMPLEMENTS: 4   TEMPORAL_NEXT: 4   TESTS: 4   DERIVED_FROM: 3
SUPPORTS: 2   CITES: 2   CONTRADICTS: 2   SUPERSEDES: 2   RELATES_TO: 2

_build_pattern("follows"|"comes after"|"is after"|"succeeds", EDGE_TEMPORAL_NEXT) at triple_extractor.py:154-157. Combined with EXCLUDED_WRITERS asserting temporal_spine "writes TEMPORAL_NEXT only — the spine, recomputed by #1336", the manifest represents the whole TEMPORAL_NEXT population as accounted for by the spine recompute when a second, prose-driven writer also mints it. This one has consequences outside the manifest — see the bottom.

3. --axes-budget is a first-order decider and is not pinned or named

budget=24 on analyze_gaps/build_dispatch_payload (dispatch.py:203, :426, surfaced at cli.py:8190) caps the anchor tuple, i.e. how many RELATES_TO edges each persisted phantom writes. Drop it to 8 and the RELATES_TO population — the largest non-spine type on that lane — falls ~3x, with all 31 tests green, the digest untouched and the version still 1.

It does not fit a (module, name) scheme because it is a signature default, not a module constant. That is the same shape as the weight=1.0 gap you already document and defer, so I have not touched it — but it is currently in neither the manifest nor the documented-omissions list, and it is a bigger lever than several constants that are pinned. Either name it as a module constant (behaviour-preserving, closes it properly) or add it to the docstring's omissions.

4. The premise "belief set and thresholds" is missing a third input

For CONTRADICTS, the per-belief write-gate makes the surviving edge set depend on belief arrival order. Two stores with byte-identical belief sets and every pinned threshold at its manifest value can hold different CONTRADICTS edges — a full-store write_semantic_edges() versus the shipped incremental write_semantic_edges(new_belief_ids=[…]) path (relationship_detector.py:1001-1015). So pinning every listed constant is necessary but not sufficient for recompute, and anyone using this manifest to check "edges are recomputable" by re-deriving from beliefs + thresholds gets a false mismatch on any incrementally built store — which is every real one.

DEFAULT_MAX_EDGES_PER_BELIEF's gates text works against this: "Pairs are processed in deterministic audit order, so which edges survive the cap is deterministic too" is true only within one call. Worth a paragraph in the limits section — it is the honest boundary of what the manifest buys, and the module is otherwise good about naming those.


Pushed (5 commits, rebased on main, FF-ready)

commit what
aae3c41c pin_value and size_of disagreed on whether a bool is a scalar. size_of(True)None (scalar); pin_value(True) → a sha256: digest. A pinned boolean would carry size=None and a digest — the exact pair test_scalar_entries_pin_a_literal_not_a_digest rejects — so no boolean could be added without a spurious failure. The exclusion bought nothing: json.dumps already renders True as true and 1 as 1. Test added, mutation-checked. This is the Sourcery thread, but the consequence is the inconsistency rather than the docstring.
48f4ca1f CodeQL is right about Sequence — referenced only inside a string-literal cast, so never evaluated. Moved under TYPE_CHECKING; pyright still resolves the cast, 0 errors.
3fae2a6e Two evasions in the coverage sweep. _RAW_SQL_RE matched only bare/OR-qualified INSERT INTO edges, so REPLACE INTO edges walked past both arms — and REPLACE is what a new writer reaches for first, since insert_edge is a bare INSERT against a PRIMARY KEY (src,dst,type) and raises on re-write. INSERT INTO "edges", [edges], main.edges also evaded, and the qualified form is house style (INSERT INTO temp.fts, store.py:3752). Widened; still exactly ["aelfrice.store"] on the live tree, and a trailing \b drops a false positive the old pattern had on edges_backup. Also: the digest covers version + THRESHOLDS but not the coverage lists, so a module can move covered → excluded with no digest and no version movement, silently voiding test_covered_modules_all_have_entries for it while its entries stay in THRESHOLDS. Asserted. Both mutation-verified: green before, red after.
8f77cf97 The "Two limits" paragraph sat unindented between two list items, which terminates the list — it rendered under ### Added rather than under its entry, and restarted the list below. Only such paragraph across all five changelog files.
0c86989b 22 constants across 9 modules, not 8.

Full suite green on the rebase; pyright 0 errors; discretion grep on added lines clean.

Not for this PR — aelf spine clear silently reverses prose-derived edges

Falling out of finding 2's last item, and filed separately since it is a live data path, not a manifest question:

ingest "deployment phase two succeeds deployment phase one"
  TEMPORAL_NEXT:  d74b6a6b -> 0f0f6740     (successor -> predecessor, per triple_extractor.py:148)
clear_temporal_spine()  removed 1
backfill_temporal_spine()  wrote 1
  TEMPORAL_NEXT:  0f0f6740 -> d74b6a6b     reversed

clear_temporal_spine deletes by type (temporal_spine.py:343), so it takes the triple-extractor rows with it; the backfill then rebuilds a session-chronological chain pointing the other way. One edge before, one after — no count-based check sees it — and clear_temporal_spine's docstring claims "a later backfill rebuilds them byte-identically (the G5 determinism property)", which does not hold for these rows. Reachable from aelf spine clear (cli.py:6012). May also account for part of #1356's shipped - recomputed bucket.

@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:10:51Z]

Comment thread src/aelfrice/detector_thresholds.py Fixed
@robotrocketscience

Copy link
Copy Markdown
Owner Author

All four accepted, three commits pushed. You were right about the headline claim.

Reproduced every finding against live source before acting on it. Nothing here is disputed.

1. The digest did not force a version bump — fixed

You're right, and it's the worst kind of wrong: the module was named after the property it didn't have, and arm 2's own failure message instructed the repair that defeats it. A lone MANIFEST_DIGEST literal sitting beside the thing it digests forces nothing.

My mutation table row relax a pinned value | 2 failed is exactly the stop-one-edit-short you identified. It changed the manifest entry and not the digest, so it never reached the state a real author would be in thirty seconds later.

Took the first option, since #1355's criterion is "forces":

DIGEST_HISTORY: Final[dict[int, str]] = {1: "ffaaca91…"}
MANIFEST_DIGEST: Final[str] = DIGEST_HISTORY.get(DETECTOR_THRESHOLDS_VERSION, "")

manifest_digest() is now content-only — the version is the key, so folding it into the payload would move the digest for reasons unrelated to what the manifest says. Two additions beyond your sketch:

  • .get, not a subscript. Bumping without appending is a contract breach the tests should name; an import-time KeyError would take the module down and surface as a collection error in every unrelated test that imports it.
  • A contiguity armset(DIGEST_HISTORY) == {1..VERSION}, no duplicate digests — so a bump cannot skip a row or reuse one.

Your repro, re-run with __pycache__ cleared between every step:

step before after
source 0.4 → 0.9 only 1 failed 1 failed
+ matching manifest entry edit 31 passed 1 failed
+ bump version, no row appended 2 failed, cleanly
+ append the row 32 passed

All four sites that stated it falsely are rewritten, including CHANGELOG/v4.md, and all four now carry the limit you named: overwriting a historical row still works, and only a merge-base check in CI is fully mechanical. Not bundled, per your call.

The __pycache__ warning was worth flagging. I hit the same thing earlier on this branch from the other direction — a restore step reported a stale failure for one run — and misread it as a script artifact rather than the cache. Every mutation above clears caches first.

2. Five gates strings — all five confirmed and rewritten

Checked each against source rather than accepting them:

  • SUPERSEDES_WEIGHT — no propagation arithmetic exists. Edge.weight is read by a BFS sort key, clustering.py:145, and persistence; propagation is EDGE_VALENCE, keyed on edge type, which this constant never touches. I conflated the two. Rewritten to your sharper reading: at 1.0 it clears DEFAULT_CLUSTER_EDGE_FLOOR (0.4), and anything below silently drops every SUPERSEDES edge out of candidate clustering.
  • UNCERTAINTY_THRESHOLD — confirmed, anchors = tuple(b.id for b in ga.known_beliefs) is unfiltered. Rewritten to the high_uncertainty_beliefsuncertainty_deep_dive axis path, and it now says explicitly that it does not filter the anchor tuple.
  • QUANT_AXIS — confirmed the 2x error. q_term halves the distance and score halves it again. The text now says a quarter, gives your always/sometimes → 0.25 case, and names the consequence: it lands as POTENTIALLY_STALE, the wrong side of the split the neighbouring entry exists to record.
  • DEFAULT_JACCARD_MIN — confirmed. Pool monotonic, written set not, because the per-belief cap is spent in sorted pair order.
  • _PATTERNS — confirmed, 4 of 25 patterns at triple_extractor.py:154-157. TEMPORAL_NEXT added to edge_types.

On your "consequences outside the manifest": agreed, and I moved it into EXCLUDED_WRITERS rather than leaving it in the _PATTERNS entry, because that's where the misleading sentence was. "Writes TEMPORAL_NEXT only" now reads as a statement about that module, not about the type, and names triple_extractor as a second prose-driven producer the #1336 spine recompute does not account for. That is worth a look independent of this PR — if the recompute's reproduction figure is computed over the whole TEMPORAL_NEXT population, those prose-minted edges are unreproducible by construction and sit in the residual.

3 & 4. Both recorded as limits

--axes-budget is in the documented-omissions list with your framing — bigger lever than several pinned constants, but a signature default, out of reach of the (module, name) scheme for the same reason as the inline weights. I did not promote it to a module constant; that is a behaviour-preserving refactor and belongs in its own change.

The arrival-order point is now its own paragraph, stated as you put it: pinning these is necessary, not sufficient. Two stores with identical belief sets and every value at its manifest reading can hold different CONTRADICTS edges, so anyone re-deriving from beliefs plus this manifest gets a false mismatch on any incrementally built store — every real one. I also softened DEFAULT_MAX_EDGES_PER_BELIEF's "deterministic too", which was true only within one call.

Verification

pytest 7269 passed, 70 skipped, 71 xfailed. pyright 0 errors on both files. Discretion grep clean. Three commits, split by concern rather than by review round — the digest mechanism, the gates corrections, and the changelog.

Leaving attn:unblock for you to clear rather than clearing it myself, since you own the finding.

Comment thread src/aelfrice/detector_thresholds.py Fixed
@robotrocketscience
robotrocketscience force-pushed the feat/issue-1355-detector-threshold-manifest branch from d7ad9d3 to cadfd5d Compare August 6, 2026 00:34
@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

Unblocked. The only outstanding thread was CodeQL alert 557 (unused Sequence import at detector_thresholds.py:103) — a false positive in the sense that the name was live, but only inside the string-literal cast("Sequence[Any]", obj) at line 161, which CodeQL cannot see through.

Fixed at cadfd5da by narrowing the cast to cast("tuple[Any, ...] | list[Any]", obj) and deleting the TYPE_CHECKING block. That is strictly more precise than the old annotation: the enclosing branch is already isinstance(obj, (tuple, list)), so Sequence was weaker than what the guard proves.

Rebased onto main (12 commits, all signed). CI green, 0 unresolved threads. Returning to attn:review — I authored this, so it needs a sister's eyes before ready-to-merge.

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

Review — two fixes pushed, two body numbers corrected

Reviewed at 144fca14 (the branch moved twice while I worked — 0cdae131, then
144fca14; my commit is rebased on top). I re-ran the mutations myself rather
than reading the tables, and confirm the core claim: mutating
DEFAULT_JACCARD_MIN 0.4 → 0.9 turns exactly one new test red while the
pre-existing test_config_loader_overrides_and_falls_back stays green. That is
the defect #1355 was filed on and this PR closes it.

Pushed to your branch — db212b04

1. A code comment stated the opposite of what the module does.
tests/…_1355.py:300 read "manifest_digest() covers the version and
THRESHOLDS". It does not: the payload is {"thresholds": ...}, content-only by
design and by its own docstring, and DETECTOR_THRESHOLDS_VERSION does not
appear in it — verified, VERSION in payload == False. The version bump is
forced by DIGEST_HISTORY being keyed by version, not by the digest covering
it. The comment's conclusion still holds, so only its stated reason was wrong —
wrong in the way that matters most, because it reads as verified.

2. The scalar filter could go quietly vacuous. test_scalar_entries_pin_a_ literal_not_a_digest selected on the raw strings {"numeric_cutoff", "cap", "weight", "literal"} while KINDS was already imported and the KIND_*
constants exist. This is not hypothetical — I measured it:

with KIND_CUTOFF renamed "numeric_cutoff" -> "numeric_floor":
  filter via KIND_* constants : selects 11 scalar entries   <- correct
  filter via raw strings      : selects  6 scalar entries   <- 5 silently dropped

So a rename doesn't fail the guard, it shrinks it — the test stays green while
covering half of what it claims. Now selected through the constants. (Sourcery
raised this in its review body rather than as a thread, which is why it was
still open.) 32/32 green after both changes.

Corrected in the PR body

Both were stale relative to the head, in the same direction — the body described
an earlier revision:

  • Mutation table, "version bump alone, no appended row | 1 failed"2
    failed
    . I ran it: test_manifest_digest_is_pinned and
    test_digest_history_is_contiguous_and_complete. The row predates the
    single-literal MANIFEST_DIGESTDIGEST_HISTORY redesign.
  • Verification section, "New file: 30 passed"32 passed. The bool fix in
    aae3c41c added test_pin_value_and_size_of_agree_on_what_a_scalar_is.

The "22 constants across 8 modules" claim I was going to flag is already
correct at head — it reads 9, matching CHANGELOG/v4.md and the artifact
(len({t.module for t in THRESHOLDS}) == 9).

One thing I did not change — your call

MANIFEST_DIGEST uses DIGEST_HISTORY.get(VERSION, ""), so on an un-appended
version bump this public, __all__-exported constant silently becomes "" for
any external reader. The tests use bracket indexing and do fail, so the suite is
protected; the exported surface is not. Low severity, and the .get is there to
dodge an import-time KeyError, so the fix isn't free — flagging rather than
deciding it for you.

Merge-readiness

FF-ahead of main, no unresolved threads, 3 files (CHANGELOG/v4.md, the new
module, the new test), no production behaviour change, no constant edited.

Worth stating explicitly for whoever picks up the next issue: once this lands,
any change to a pinned constant in relationship_detector, contradiction,
triple_extractor, value_compare, wonder.*, bm25 or models costs an
extra commit
— manifest update, DETECTOR_THRESHOLDS_VERSION bump,
DIGEST_HISTORY append. That is the guard working as designed, and it is why
this should merge before #1368, #1376 and #1380 rather than after them.

Taking it to ready-to-merge once CI settles on the rebase.

@robotrocketscience
robotrocketscience force-pushed the feat/issue-1355-detector-threshold-manifest branch from db212b0 to 0b6af03 Compare August 6, 2026 01:01

@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 (1)
tests/test_detector_thresholds_manifest_1355.py (1)

138-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Select scalars by the imported kind constants, and assert the selector is not empty.

Line 139 hardcodes the kind strings. KIND_CUTOFF, KIND_CAP, KIND_WEIGHT and KIND_LITERAL are already exported by the module under test. If a kind value is renamed, this set matches no entry and the loop body never runs, so the test passes while asserting nothing. test_entries_are_wellformed_and_unique stays green in that case because it checks membership in the imported KINDS. That is the same "test that cannot fail" shape this file's docstring targets.

Import the constants and add a floor so the selector cannot go empty.

♻️ Proposed change
 from aelfrice.detector_thresholds import (
     COVERED_WRITER_MODULES,
     DETECTOR_THRESHOLDS_VERSION,
     EXCLUDED_WRITERS,
+    KIND_CAP,
+    KIND_CUTOFF,
+    KIND_LITERAL,
+    KIND_WEIGHT,
     KINDS,
+    _SCALAR_KINDS = {KIND_CUTOFF, KIND_CAP, KIND_WEIGHT, KIND_LITERAL}
+    checked = 0
     for entry in THRESHOLDS:
-        if entry.size is None and entry.kind in {"numeric_cutoff", "cap", "weight", "literal"}:
+        if entry.size is None and entry.kind in _SCALAR_KINDS:
+            checked += 1
             assert not entry.value.startswith("sha256:"), (
                 f"{entry.module}.{entry.name} is a scalar and must pin its "
                 f"literal, not a digest"
             )
+    assert checked, "no scalar entry was examined — the selector matched nothing"

Note: KIND_CAP, KIND_CUTOFF, KIND_LITERAL and KIND_WEIGHT are not in the module's __all__. Add them there, or keep the strings and add the floor assertion alone.

🤖 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_detector_thresholds_manifest_1355.py` around lines 138 - 143,
Update the scalar selector in the threshold test to use the exported
KIND_CUTOFF, KIND_CAP, KIND_WEIGHT, and KIND_LITERAL constants instead of
hardcoded strings; add these constants to the module’s __all__ if required for
import, and assert that at least one THRESHOLDS entry matches the selector so
the test cannot pass vacuously.
🤖 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/detector_thresholds.py`:
- Around line 742-754: Reorder the entries in the module-level __all__ list into
isort order, moving "DIGEST_HISTORY" before "EXCLUDED_WRITERS" and preserving
all existing exports unchanged.

In `@tests/test_detector_thresholds_manifest_1355.py`:
- Around line 310-315: Correct the comment near the manifest consistency
assertion to state that manifest_digest() covers only THRESHOLDS, not the
version, while preserving the existing explanation about coverage-list moves and
the test_covered_modules_all_have_entries assertion.

---

Nitpick comments:
In `@tests/test_detector_thresholds_manifest_1355.py`:
- Around line 138-143: Update the scalar selector in the threshold test to use
the exported KIND_CUTOFF, KIND_CAP, KIND_WEIGHT, and KIND_LITERAL constants
instead of hardcoded strings; add these constants to the module’s __all__ if
required for import, and assert that at least one THRESHOLDS entry matches the
selector so the test cannot pass vacuously.
🪄 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: 58f38134-8890-4139-ae3c-9eb453df2460

📥 Commits

Reviewing files that changed from the base of the PR and between d3e5f04 and 144fca1.

📒 Files selected for processing (3)
  • CHANGELOG/v4.md
  • src/aelfrice/detector_thresholds.py
  • tests/test_detector_thresholds_manifest_1355.py

Comment thread src/aelfrice/detector_thresholds.py
Comment thread tests/test_detector_thresholds_manifest_1355.py Outdated
@robotrocketscience
robotrocketscience force-pushed the feat/issue-1355-detector-threshold-manifest branch from 0b6af03 to db212b0 Compare August 6, 2026 01:01
…edges

#1283 restated AC2 in two halves. The recompute half shipped in #1336;
this is the other one. Edges that are neither TEMPORAL_NEXT nor
DERIVED_FROM are a function of the belief set AND of detector
thresholds, so "edges are recomputable" holds for them only if those
thresholds are pinned and versioned. They were bare module constants
with no guard.

detector_thresholds records 22 constants across 8 modules behind
DETECTOR_THRESHOLDS_VERSION: the relationship_detector cutoffs, caps
and vocabularies, the contradiction precedence ladder, the
triple_extractor phrase-to-edge-type patterns, the value_compare slot
gate, and the constants on the two paths that decide which phantoms
reach the RELATES_TO writer.

The module holds hand-written literals and imports nothing from
aelfrice. That is deliberate: importing the constants it describes
would make it tautological in exactly the way the tests it replaces
were, and would drag store/bm25 into the import graph of any reader.
Scalars pin as literals so a reviewer can check them by eye;
collections pin as a digest of a canonical form that includes regex
flags, since dropping re.IGNORECASE changes which triples match
without changing any pattern text.

Entries were checked for reachability rather than assumed. The wonder
bake-off constants are NOT pinned -- the package docstring states the
strategies are research-only and their sole importer builds against an
in-memory store, so they decide no edge a user holds. The value_compare
entries are pinned but labelled dormant: nothing shipped passes
use_value_comparison=True. Three upstream suppliers the call-site sweep
cannot see (bm25._TOKEN_PATTERN, models.ANCHOR_TEXT_MAX_LEN,
wonder_consolidation._TOKENIZER_DROP) are pinned by hand, and the two
suppliers left unpinned are named so their absence is a decision.

Forward-only. The edges table has no version and no created_at, so
this does not make a historical edge attributable to the thresholds
that produced it; adding those columns is the migration that bricked
stores in #1161, and historical reproduction stays out of scope.
…or unpinned writers

Three independent failure modes, three arms that fail independently.

Arm 1 re-derives each pinned value from the live constant by importing
it, so the comparison is manifest-literal against source rather than
symbol against itself. This is the defect the issue was filed on:
test_config_loader_overrides_and_falls_back asserts
cfg.jaccard_min == DEFAULT_JACCARD_MIN, and changing the constant from
0.4 to 0.9 leaves it green -- measured, not asserted.

Arm 2 pins a digest taken over the manifest INCLUDING its version,
which is what makes "changing a value forces a version bump"
mechanical rather than conventional: a value edit fails twice, and the
only route back to green moves MANIFEST_DIGEST next to the version.

Arm 3 sweeps insert_edge call sites out of source text rather than
comparing against a hand-list, so a writer landing later fails here
instead of sitting silently outside the manifest. A companion arm
asserts only the store writes the edges table directly, so the sweep
cannot be evaded with raw SQL. Covered-ness matches on the exact
module, not a package prefix -- prefix matching is what let
wonder.lifecycle look covered by wonder.evaluator, a research-only
module that writes to no live store.

Verified by mutation, eight cases: five source-side (a cutoff, a token
added to a negation set -- red twice, since the stopword set is a
union of it -- dropping re.IGNORECASE from a triple pattern, widening
the bm25 token pattern, and shortening the anchor cap) and three
manifest-side (a version bump alone, relaxing a pinned value to match
a drift, and a comment-only edit that correctly stays green because
the digest covers data rather than source text).
`pin_value` excluded bool from its scalar branch and digested it, while
`size_of` reports None (scalar) for a bool. A pinned boolean constant would
therefore have carried `size=None` and a `sha256:` value at once — exactly the
combination `test_scalar_entries_pin_a_literal_not_a_digest` rejects — so no
boolean could be added to the manifest without a spurious failure. The
exclusion bought nothing: json renders True as `true` and 1 as `1`, so the two
were never ambiguous. Asserted directly, since no boolean is pinned today.
`Sequence` is referenced only inside a string-literal `cast`, so the name is
never evaluated at runtime and the unconditional import was dead — CodeQL
flagged it. Moving it under TYPE_CHECKING keeps pyright resolving the cast
while removing the runtime import.
The paragraph sat unindented between two list items, which terminates the list
in Markdown: it rendered as a standalone paragraph belonging to `### Added`
rather than to its entry, and restarted the list below it. It is the only such
paragraph in the five changelog files. Two spaces makes it a continuation of
the bullet it belongs to; no text changed.
The PR body claims the sweep "cannot be evaded with raw SQL" and that keeping
the raw-SQL arm "means arm 3 cannot be quietly defeated". Neither held.

`_RAW_SQL_RE` matched only a bare or `OR`-qualified `INSERT INTO edges`, so
`REPLACE INTO edges` evaded it — and REPLACE is the spelling a new writer is
most likely to reach for, since `insert_edge` issues a bare INSERT against a
`PRIMARY KEY (src, dst, type)` table and raises on re-write. `INSERT INTO
"edges"`, `[edges]` and `main.edges` evaded it too, and the qualified form is
already house style (`INSERT INTO temp.fts` in store.py). Such a module is
invisible to both arms at once: no `.insert_edge(` for the call-site sweep,
no match here. Verified by adding a `REPLACE INTO edges` module — green
before, red after — and the widened pattern still resolves to exactly
["aelfrice.store"] on the live tree. A trailing `\b` also drops a false
positive the old pattern had on `edges_backup`.

Separately, `manifest_digest()` covers the version and THRESHOLDS but not the
two coverage lists, so moving a module from covered to excluded moves no
digest and no version. That silently voids
`test_covered_modules_all_have_entries` for the module while its entries stay
in THRESHOLDS claiming to gate edges the exclusion says it does not decide.
Asserted directly; mutation-verified.
`{t.module for t in THRESHOLDS}` is 9: bm25, contradiction, models,
relationship_detector, triple_extractor, value_compare, wonder.dispatch,
wonder.lifecycle, wonder_consolidation. The count is prose on both sides and
nothing gates it; MANIFEST_DIGEST already goes red on any entry change, so
this is a correction rather than a new assertion.
…cannot be absorbed

Review found the module's headline claim false, and it was: a lone
MANIFEST_DIGEST literal sitting beside the thing it digests forces
nothing. Edit a constant, edit its manifest entry, edit the digest, and
the suite is green with DETECTOR_THRESHOLDS_VERSION untouched -- two
different edge-producing behaviours shipping as version 1. That is
verbatim failure mode 2 in the test file's own docstring, and the arm's
failure message instructed exactly the repair that causes it.

Replaces the literal with DIGEST_HISTORY, keyed by version, and digests
content only (the version is now the key, so folding it into the payload
would move the digest for reasons unrelated to what the manifest says).
The cheap repair is gone: the ways back to green are to revert, or to
bump and APPEND a row. A second arm pins the history contiguous over
1..VERSION with no duplicate digests, so a bump cannot skip a row.

MANIFEST_DIGEST is derived via .get rather than a subscript -- bumping
without appending is a contract breach the tests should name, and an
import-time KeyError would take the module down and surface as a
collection error in every unrelated test that imports it.

Stated rather than overclaimed: overwriting a historical row still
works. Only a merge-base check in CI is fully mechanical, and that is
not bundled here.

Verified by mutation with caches cleared between runs (same-length
digests are otherwise served from a stale .pyc): the reported repro --
source 0.4 -> 0.9 plus the matching manifest edit -- previously passed
31/31 and now fails; bumping without appending fails two arms cleanly;
bumping and appending is green.
…usion

`gates` is the manifest's substance -- "a constant that cannot answer
this does not belong here" -- and nothing tests these strings, so five
wrong ones shipped. All five were reproduced against live source before
being rewritten.

- SUPERSEDES_WEIGHT claimed "propagation arithmetic". There is none:
  Edge.weight is read by a BFS sort key, a clustering floor and
  persistence. Propagation is EDGE_VALENCE, keyed on edge TYPE, which
  this constant does not touch. The real effect is sharper -- at 1.0 it
  clears DEFAULT_CLUSTER_EDGE_FLOOR (0.4), and any value below that
  silently drops every SUPERSEDES edge out of candidate clustering.
- UNCERTAINTY_THRESHOLD claimed to filter the anchor tuple. It does not:
  `anchors` is built from the unfiltered `known_beliefs`. It selects
  high_uncertainty_beliefs, which decides whether an
  uncertainty_deep_dive axis is emitted -- still a real path to
  RELATES_TO, but not the stated one.
- QUANT_AXIS said the score is half the axis distance. It is a quarter:
  q_term halves the distance and the score halves it again. `always` vs
  `sometimes` is 1.0 apart and scores 0.25, so it lands as
  POTENTIALLY_STALE rather than CONTRADICTS -- the wrong side of the very
  split DEFAULT_CONFIDENCE_MIN exists to record. Sizing an edit with the
  old prose picks the wrong value.
- DEFAULT_JACCARD_MIN claimed lowering it "can only add edges". The
  candidate pool is monotonic; the written set is not, because
  DEFAULT_MAX_EDGES_PER_BELIEF is spent in sorted pair order, so a
  newly-admitted pair can evict a previously written one.
- _PATTERNS omitted TEMPORAL_NEXT from edge_types while four of its 25
  patterns mint exactly that type (`follows`, `comes after`, `is after`,
  `succeeds`).

That last one has a consequence outside the manifest, so the exclusion
entry now carries it: EXCLUDED_WRITERS said temporal_spine "writes
TEMPORAL_NEXT only", which reads as the spine accounting for the whole
TEMPORAL_NEXT population. It does not -- triple_extractor is a second,
prose-driven producer the #1336 spine recompute does not cover.

Also records two limits the manifest was silent on. `--axes-budget`
(default 24) caps the anchor tuple and so how many RELATES_TO edges each
persisted phantom writes -- a bigger lever than several pinned constants,
but a signature default rather than a module constant, out of reach of
the (module, name) scheme for the same reason the inline weights are.
And belief ARRIVAL ORDER is a third input beside the belief set and these
thresholds: the per-belief cap is spent on whichever pairs arrived first,
so full-store and incremental runs can disagree on identical beliefs, and
re-deriving edges from beliefs plus this manifest gives a false mismatch
on any incrementally built store -- which is every real one.
The string-literal cast to Sequence kept a TYPE_CHECKING import alive
that CodeQL reads as dead (alert 557). The branch is already narrowed
to tuple|list by the isinstance guard above it, so casting to that
union is both stricter and self-contained.
…e one

assert MANIFEST_DIGEST == DIGEST_HISTORY[VERSION] compared
DIGEST_HISTORY.get(VERSION, "") against DIGEST_HISTORY[VERSION] — true
by definition of .get whenever the key exists, which the contiguity test
already guarantees. It could never fail.

The case the .get fallback exists for — a version bump with no appended
row — reached the preceding subscript first and reported as a KeyError,
the crash the fallback was added to avoid. Assert the constant resolved
before subscripting anything, and compare against it thereafter.

Mutation-checked both directions: bumping the version without a row now
names the missing row; changing a pinned value without bumping still
names the digest mismatch.
…filter (#1355)

Two fixes in the manifest test.

The comment above the covered/excluded overlap assert said
`manifest_digest()` 'covers the version and THRESHOLDS'. It does not --
the payload is content-only by design and `DETECTOR_THRESHOLDS_VERSION`
does not appear in it. The version bump is forced by `DIGEST_HISTORY`
being keyed by version. The comment's actual conclusion still holds, so
only its stated reason was wrong -- wrong in a way that reads as
verified.

The scalar filter selected on the raw strings {'numeric_cutoff', 'cap',
'weight', 'literal'} while KINDS was already imported and the KIND_*
constants exist. Renaming any KIND_* value made the filter match fewer
entries and the guard go quietly vacuous rather than red: with
KIND_CUTOFF renamed, the raw-string filter selects 6 of the 11 scalar
entries it should. Selecting through the constants keeps all 11.
@robotrocketscience
robotrocketscience force-pushed the feat/issue-1355-detector-threshold-manifest branch from db212b0 to cff7be8 Compare August 6, 2026 01:03
`DIGEST_HISTORY` sat after `KINDS`. No behaviour change -- `__all__`
only governs star-imports and the module has no star-importer.

Taken on convention, not on the reason the review gave: there is no ruff
configuration and no lint job in this repo, so RUF022 is not enforced and
no gate was failing. 19 of the 22 modules on main that declare a
multi-entry `__all__` keep it sorted, which is reason enough on its own.
@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 merged commit ec25ee6 into main Aug 6, 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 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

merge-train: merged ec25ee6main via FF push.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Garsecg:2026-08-06T01:10:39Z]

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-Setr PR coordination mutex ready-to-merge Trigger merge-train: FF main to this PR's head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(detectors): pin and version the thresholds behind the 2.8% non-spine edges (#1283 AC2)

2 participants