feat(miner-governor): closed-loop discovery re-entry trigger (#2338) - #5051
Conversation
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gittensory-ui | 53b3f4f | Commit Preview URL Branch Preview URL |
Jul 11 2026, 12:42 PM |
ad3d32e to
d5fae4d
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5051 +/- ##
=======================================
Coverage 94.13% 94.14%
=======================================
Files 466 467 +1
Lines 39573 39587 +14
Branches 14436 14442 +6
=======================================
+ Hits 37254 37268 +14
Misses 1664 1664
Partials 655 655
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
Tip 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 ✅ Gittensory review result - approve/merge recommendedReview updated: 2026-07-11 12:45:25 UTC
✅ Suggested Action - Approve/Merge
Review summary Nits — 1 non-blocking
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
|
@gittensory chat what does this PR add? |
d5fae4d to
5cac1ab
Compare
|
@gittensory chat tell me about this PR |
Adds shouldReenter (packages/gittensory-engine/src/miner/loop-reentry- policy.ts) + attemptLoopReentry (packages/gittensory-miner/lib/loop- reentry.js): the final piece that turns single miner runs into an unattended repeat loop -- on a resolved outcome (merged, or rejected- and-disengaged), decide whether to re-enter discovery and, when allowed, actually dequeue the next candidate and transition run-state. Not wired into any scheduler/cron as part of this change, per the issue's own "manual owner sign-off before enabling by default in any profile" deliverable. Two independent hard ceilings, per the issue's own top-slop-at-scale- risk framing (re-entering too fast, or looping on a permanently- rejected repo): - A per-repo circuit breaker: N consecutive disengaged (closed- without-merge) pr_outcome events for a repo pauses further re-entry for that repo specifically, computed from real pr-outcome.js history via countConsecutiveDisengagements (walks backward from the most recent PR until a merged outcome breaks the streak). - A hard rate/session cap: independent of any repo's own history, a conservative ceiling (default 4/hour, 20/session) on re-entries, computed from real event-ledger history via countReentriesSince. Every call appends exactly one loop_reentry_decision event to the ledger, whether or not re-entry was allowed, with the full reason set and (when the caller supplies one) the just-completed cycle's loop-closure.js LoopClosureSummary threaded through verbatim for traceability -- auditable regardless of this function's own return value. Discovered and fixed while wiring this in: 10 other packages/ gittensory-miner/lib/*.js files (governor-kill-switch, loop-closure, pr-outcome, rejection-state-machine, and others -- none from this session) were missing from the package's own `node --check` build script, an accumulating pre-existing gap. Added all of them alongside this issue's own loop-reentry.js. Test-covered per the issue's own explicit deliverable: "merged outcome -> re-entry fires once; rejected outcome with high repeated-blocker tally -> re-entry is suppressed and the repo is paused" (both test names verbatim), plus the independent rate/session caps, fail-closed validation, and the optional loopSummary/runState threading.
…ift in pr-outcome.d.ts
pr-outcome.d.ts's RecordPrOutcomeOptions.eventLedger hand-declared a looser
{ appendEvent(event: { ...payload: unknown }): unknown } shape instead of reusing
EventLedger's real AppendEventInput/LedgerEntry contract, so a genuine EventLedger
(from initEventLedger) never type-checked against it -- only a same-shaped stub did.
This branch's own test was the first to pass a real EventLedger in, surfacing the gap.
Fixes it at the source (import + reuse the real types) and updates the one other
mock ledger (miner-pr-outcome.test.ts) that relied on the looser shape so it can't
silently drift from the real contract again. Also restores the two `as never` casts
missing from two deliberately-invalid-deps assertions in miner-loop-reentry.test.ts
that were inconsistent with the identical pattern on the four lines directly above them.
No behavior change -- purely type-declaration correctness; the runtime fail-closed
behavior these tests assert was always correct and remains covered.
5cac1ab to
53b3f4f
Compare
Summary
Closes #2338.
Adds
shouldReenter(packages/gittensory-engine/src/miner/loop-reentry-policy.ts) +attemptLoopReentry(packages/gittensory-miner/lib/loop-reentry.js): the final piece that turns single miner runs into an unattended repeat loop -- on a resolved outcome (merged, or rejected-and-disengaged), decide whether to re-enter discovery and, when allowed, actually dequeue the next candidate and transition run-state.Per this issue's own "manual owner sign-off before enabling by default in any profile" deliverable, this is not wired into any scheduler/cron as part of this change -- it's a callable, fully-tested function ready for that sign-off.
Two independent hard ceilings, matching the issue's own framing of the top slop-at-scale risk (re-entering too fast, or looping on a permanently-rejected repo):
pr_outcomeevents for a repo pauses further re-entry for that repo specifically -- computed from realpr-outcome.jshistory viacountConsecutiveDisengagements(walks backward from the most recent PR until a merged outcome breaks the streak).countReentriesSince.Every call appends exactly one
loop_reentry_decisionevent to the ledger, whether or not re-entry was allowed, with the full reason set -- and, when the caller supplies one, the just-completed cycle'sloop-closure.jsLoopClosureSummarythreaded through verbatim into the payload for traceability (aLoopClosureSummary's ownbyTypecounts aren't detailed enough to compute the circuit-breaker tally from directly, so it's threaded as context, not a computational input).On the file paths: the issue names
packages/gittensory-miner/src/manage/loop-reentry.ts, but thatsrc//TypeScript layout doesn't exist anywhere inpackages/gittensory-miner(confirmed by search) -- the established, actual convention (already used byloop-closure.js,rejection-state-machine.js,pr-outcome.js, and every governor primitive this wave) is flatlib/*.js+ hand-written.d.ts, validated vianode --check. Followed that instead, plus this session's own engine(pure)/miner-lib(stateful) split for the decision vs. the IO.Discovered and fixed while wiring this in: 10 other
packages/gittensory-miner/lib/*.jsfiles (governor-kill-switch.js,loop-closure.js,pr-outcome.js,rejection-state-machine.js, and others -- none from this session) were missing from the package's ownnode --checkbuild script, an accumulating pre-existing gap unrelated to this issue. Verified all 10 passnode --checkcleanly, then added all of them to the script alongside this issue's ownloop-reentry.js.Validation
Measured, not assumed -- across BOTH the engine's own
node:testsuite AND the actual codecov-visible root vitest path (see the local memory note from earlier in this batch about the engine package's Codecov blind spot):loop-reentry-policy.js100.00% lines/branch/funcs.loop-reentry.jsandloop-reentry-policy.tsat 100.00% stmts/branch/funcs/lines.packages/gittensory-miner'snode --checkbuild script passes cleanly with all 11 newly-registered files.Test plan
Per the issue's own explicit deliverable, named verbatim:
loopSummaryis threaded through verbatim when supplied, and recorded asnull(not omitted) when not.runStatedependency.