Skip to content

engine(signals): predicted-gate-engine's classifyBountyLifecycle uses unanchored regexes, regressing the #9080 fix #10313

Description

@JSONbored

⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.

Context

packages/loopover-engine/src/signals/engine.ts and
packages/loopover-engine/src/signals/predicted-gate-engine.ts both export a function named
classifyBountyLifecycle, and they have drifted from each other.

signals/engine.ts (lines 3841-3854) uses word-boundary-anchored regexes:

const BOUNTY_CANCELLED_STATUS_RE = /\b(cancel|void|expired|withdrawn|rejected|abandon)/;
const BOUNTY_COMPLETED_STATUS_RE = /\b(complete|paid|resolved|rewarded|awarded|fulfil|merged|claimed|done)/;
const BOUNTY_HISTORICAL_STATUS_RE = /\b(historical|archived|closed)/;
const BOUNTY_ACTIVE_LOOKING_STATUS_RE = /\b(open|active|live|available|ready|funded|reward|award|in[\s_-]?progress|todo|new)/;

test/unit/signals.test.ts (line 913, #9080) has a regression test with the explicit comment:
"the lifecycle regexes were unanchored substring matches, so a base word buried mid-token
false-positived" — proving these regexes were deliberately anchored to fix a real bug.

predicted-gate-engine.ts's classifyBountyLifecycle (lines 557-570) inlines the same words but
without the \b anchor:

if (/cancel|void|expired|withdrawn|rejected|abandon/.test(status)) return "cancelled";
if (/complete|paid|resolved|rewarded|awarded|fulfil|merged|claimed|done/.test(status)) return "completed";
if (/historical|archived|closed/.test(status)) return "historical";

This silently regresses the #9080 fix in the package copy. Verified concretely by running both
regex sets directly: a bounty with status: "Unclaimed" classifies as "completed" in
predicted-gate-engine.ts (the unanchored completed regex matches the "claimed" substring
inside "unclaimed") versus "ambiguous" in signals/engine.ts (the anchored \bclaimed requires
a word boundary before "claimed", which "unclaimed" doesn't have). Same divergence confirmed for
status: "not_completed""completed" (pkg, matches the "completed" substring across the _)
vs "ambiguous" (host); status: "Avoid""cancelled" (pkg, matches the "void" substring
inside "avoid") vs "ambiguous" (host); and status: "Renewed" → active-eligible (pkg, matches
the "new" substring inside "renewed") vs "ambiguous" (host).

This function is exported and consumed by buildPreflightResult (line 449) and re-exported by
issue-quality-report.ts, feeding the miner-facing predictor in predicted-gate.ts. A genuinely
open, unclaimed bounty can be told to a contributor as "already solved" or "cancelled" because of
this regex drift. test/unit/predicted-gate-engine.test.ts has zero tests for any of the
"unclaimed"/"not_completed"/"Avoid"/"Renewed" cases that signals.test.ts already covers for
the host twin.

Requirements

  • In packages/loopover-engine/src/signals/predicted-gate-engine.ts, replace the four inline,
    unanchored regex literals inside classifyBountyLifecycle (and the looksActive regex on the same
    line pattern) with word-boundary-anchored equivalents, matching signals/engine.ts's
    BOUNTY_CANCELLED_STATUS_RE / BOUNTY_COMPLETED_STATUS_RE / BOUNTY_HISTORICAL_STATUS_RE /
    BOUNTY_ACTIVE_LOOKING_STATUS_RE exactly (same word list, same \b anchoring).
  • Do not change the function's control flow, return type (BountyLifecycle), or any other function
    in this file.
  • Do not modify signals/engine.ts — it is already correct; this issue only fixes the drifted
    package copy.

Deliverables

  • classifyBountyLifecycle in predicted-gate-engine.ts returns identical results to
    signals/engine.ts's classifyBountyLifecycle for every status string in
    test/unit/signals.test.ts's existing #9080-tagged test cases (lines 893-916), including
    "Unclaimed", "not_completed", "Avoid", and "Renewed" all resolving to "ambiguous" (not
    "completed"/"cancelled"/active-eligible).
  • The previously-correct behavior for genuinely matching statuses (e.g. "Rewarded"
    "completed", "Cancelled""cancelled") is unchanged.

Both Deliverables are required in this one PR — there is no narrower scope for this issue.

Test Coverage Requirements

packages/loopover-engine/src/** is measured by Codecov via two separate uploads whose hits are
unioned
— root test/** AND packages/loopover-engine/test/**. Add a regression test to
test/unit/predicted-gate-engine.test.ts (root-level, matching this file's existing test location)
mirroring test/unit/signals.test.ts's #9080 test cases (lines 908-916) exactly — same input
statuses ("Unclaimed", "not_completed", "Avoid", "Renewed"), same non-equality assertions.
Also add a passing-case assertion confirming genuinely-matching statuses still classify correctly
after the anchoring change. Target 100% branch coverage of the corrected regexes.

Expected Outcome

predicted-gate-engine.ts's classifyBountyLifecycle matches signals/engine.ts's behavior
exactly, so the miner-facing preflight predictor no longer reports a genuinely open/unclaimed bounty
as already solved or cancelled.

Links & Resources

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions