Skip to content

feat(miner-governor): kill-switch propagation into the manage/loop subsystem (#2339) - #5057

Merged
JSONbored merged 4 commits into
mainfrom
feat/miner-killswitch-manage-loop-2339
Jul 11, 2026
Merged

feat(miner-governor): kill-switch propagation into the manage/loop subsystem (#2339)#5057
JSONbored merged 4 commits into
mainfrom
feat/miner-killswitch-manage-loop-2339

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Closes #2339.

Stacked on #5051 (#2338, still open) -- this PR's diff is scoped to just the kill-switch propagation; #2338's own changes are reviewed separately in its own PR.

Propagates the existing self-host kill-switch/dry-run control into the two manage-phase decision functions the sibling maintainer issues built: shouldSubmit (submission-gate.ts, #2336) and shouldReenter (loop-reentry-policy.ts, #2338). Both now check the kill-switch as their FIRST guard, before any other logic -- flipping the kill-switch halts any pending submission-gate check or loop-reentry immediately, the same way it already halts the Governor chokepoint (#2340).

Deliverables, mapped:

  • Both functions check the kill-switch state as their FIRST guard -- done, both short-circuit (never even evaluate the rest of their own logic) when the kill-switch is active.
  • A single shared helper, not duplicated per call site -- done. Both reuse isMinerKillSwitchActive (kill-switch.ts, maintainer: global + per-repo kill-switch #2341) directly -- the same function the Governor chokepoint (maintainer: wire the fail-closed Governor chokepoint before every write action #2340) already consults -- rather than inventing a bespoke wrapper. killSwitchScope: MinerKillSwitchScope is a new REQUIRED field on both SubmissionGateCandidate and LoopReentryCandidate (not optional-with-a-permissive-default), so a caller cannot forget to resolve and pass it.
  • Read-only polling is explicitly EXEMPTED, documented and tested -- neither countConsecutiveDisengagements/countReentriesSince (loop-reentry.js) nor countConsecutiveGateBlocks (harness-submission-trigger.js) -- the read-side tally computations both wrappers use to build their audit payloads -- ever consult the kill-switch; they keep computing real tallies for accurate logging even when the kill-switch is active, and are called unconditionally regardless of kill-switch state (there's nothing to "exempt" a pure read from -- it was never gated in the first place). Documented in loop-reentry-policy.ts's own module comment.
  • Unit tests: kill-switch active blocks unconditionally regardless of otherwise-passing checks -- done, for both functions, including a test that an active kill-switch wins even over a candidate that would otherwise cleanly pass or a per-repo circuit breaker with a maxed-out tally.
  • Manual owner review -- flagging here per the issue's own deliverable, given this touches the safety-governor chokepoint.

Coupling fixups applied to the two sibling open PRs, since both shouldSubmit and shouldReenter gained a new required field their existing callers construct internally:

Validation

Measured, not assumed -- across both the engine's own node:test suite and the actual codecov-visible root vitest path:

npx tsc -p tsconfig.json --incremental false && npx tsc -p tsconfig.test.json --incremental false && node --experimental-test-coverage --test "dist-test/**/*.test.js"
  • 467/467 engine tests pass.
  • submission-gate.js: 100.00% lines/branch/funcs.
  • loop-reentry-policy.js: 100.00% lines/branch/funcs.
  • packages/gittensory-miner's node --check build script passes cleanly.
  • Root vitest test/unit/miner-loop-reentry.test.ts (the actual codecov-visible path for loop-reentry.js/loop-reentry-policy.ts): 10/10 tests pass, both files 100% branch coverage.

Test plan

  • Kill-switch (global and per-repo scope) blocks shouldSubmit unconditionally, even with an otherwise-clean predicted-gate pass and slop-under-threshold.
  • Kill-switch blocks shouldReenter unconditionally, even with a per-repo circuit breaker tally that would otherwise still permit re-entry (99 consecutive disengagements, checked before the circuit breaker itself).
  • An inactive kill-switch (scope: "none") never itself blocks -- the underlying signals are still evaluated normally.
  • Full integration test (root vitest, real SQLite-backed event-ledger/portfolio-queue/run-state): an active kill-switch blocks attemptLoopReentry without dequeuing or moving run-state, and the block is recorded to the audit ledger with the scope that caused it.

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 11, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
gittensory-ui e172927 Commit Preview URL

Branch Preview URL
Jul 11 2026, 01:04 PM

@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.18%. Comparing base (ad44723) to head (e172927).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5057      +/-   ##
==========================================
+ Coverage   94.14%   94.18%   +0.04%     
==========================================
  Files         467      468       +1     
  Lines       39609    39619      +10     
  Branches    14446    14450       +4     
==========================================
+ Hits        37289    37317      +28     
+ Misses       1664     1646      -18     
  Partials      656      656              
Flag Coverage Δ
shard-1 46.49% <10.00%> (-0.01%) ⬇️
shard-2 33.83% <10.00%> (-0.01%) ⬇️
shard-3 32.19% <30.00%> (+0.04%) ⬆️
shard-4 31.95% <10.00%> (+0.24%) ⬆️
shard-5 33.29% <30.00%> (+<0.01%) ⬆️
shard-6 44.95% <80.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...ory-engine/src/miner/harness-submission-trigger.ts 100.00% <100.00%> (ø)
...gittensory-engine/src/miner/loop-reentry-policy.ts 100.00% <100.00%> (ø)
...ges/gittensory-engine/src/miner/submission-gate.ts 100.00% <100.00%> (+90.00%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 11, 2026
@loopover-orb

loopover-orb Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩

✅ Gittensory review result - approve/merge recommended

Review updated: 2026-07-11 12:58:15 UTC

8 files · 1 AI reviewer · no blockers · readiness 100/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This PR adds a required killSwitchScope field to SubmissionGateCandidate and LoopReentryCandidate and checks it as the very first guard in both shouldSubmit (submission-gate.ts) and shouldReenter (loop-reentry-policy.ts), short-circuiting before any other logic when isMinerKillSwitchActive returns true. It reuses the existing shared helper rather than inventing a bespoke wrapper, threads the scope through the JS-side attemptLoopReentry orchestrator (loop-reentry.js) with a runtime validation guard, and adds matching unit tests for global/repo/none scopes at both the pure-policy and orchestrator layers. The diff is narrowly scoped to the stated issue (#2339), makes the field required (not optional-with-default) so callers can't silently omit it, and every changed branch shown here has a corresponding test.

Nits — 6 non-blocking
  • loop-reentry.js:63 hardcodes the scope validation array `["global", "repo", "none"]` inline rather than importing it from a single shared source (kill-switch.ts), so the union could drift silently if a new scope is ever added there.
  • submission-gate.ts has no runtime guard analogous to loop-reentry.js's `invalid_kill_switch_scope` check -- it relies entirely on TypeScript's compile-time type, so an untyped/JS caller could pass an unrecognized scope string straight into `isMinerKillSwitchActive` with no fail-closed validation at that boundary; worth confirming the (unseen) harness-submission-trigger.js wrapper validates it the same way loop-reentry.js does.
  • The near-identical `if (isMinerKillSwitchActive(...)) return {...}` short-circuit block is duplicated verbatim across submission-gate.ts and loop-reentry-policy.ts; consider a tiny shared `killSwitchGuard(scope)` helper in kill-switch.ts if a third call site shows up.
  • Export the scope literal list from kill-switch.ts and import it in loop-reentry.js's validation instead of hardcoding `["global", "repo", "none"]`.
  • Add a test (or confirm one exists in the stacked maintainer: closed-loop discovery re-entry trigger #2338/maintainer: wire the fail-closed Governor chokepoint before every write action #2340 PRs) verifying the real production call site for shouldSubmit validates killSwitchScope at its own runtime boundary, mirroring loop-reentry.js's guard.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.
Signal Result Evidence
Code review ✅ No blockers 1 reviewer
Linked issue ✅ Linked #2339
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 47 registered-repo PR(s), 39 merged, 423 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 47 PR(s), 423 issue(s).
Gate result ✅ Passing No configured blocker found.
Improvement ✅ Minor risk: clean · value: minor — Code changes are accompanied by test evidence.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 47 PR(s), 423 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.

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

  • Re-run Gittensory review

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 11, 2026
@JSONbored
JSONbored force-pushed the feat/miner-loop-reentry-2338 branch from d5fae4d to 5cac1ab Compare July 11, 2026 11:51
@JSONbored JSONbored linked an issue Jul 11, 2026 that may be closed by this pull request
5 tasks
@JSONbored
JSONbored force-pushed the feat/miner-loop-reentry-2338 branch from 5cac1ab to 53b3f4f Compare July 11, 2026 12:40
Base automatically changed from feat/miner-loop-reentry-2338 to main July 11, 2026 12:45
…bmission-gate.ts)

#2339's own deliverable: the manage-phase submission-gate function
must check the kill-switch/dry-run state as its FIRST guard, before
any other logic -- flipping the kill-switch must halt any pending
submission-gate check immediately, the same way it already halts the
Governor chokepoint (#2340).

killSwitchScope is a new REQUIRED field on SubmissionGateCandidate
(not optional-with-a-permissive-default) so a caller cannot forget to
resolve and pass it. Reuses isMinerKillSwitchActive (kill-switch.ts,
#2341) directly -- the single shared helper #2339 asks for, not a
bespoke wrapper -- rather than duplicating the check; the sibling
loop-reentry-policy.ts change (next commit) consults the identical
function.
…oop-reentry-policy.ts)

The sibling half of #2339's own deliverable: the manage-phase loop-
reentry function must ALSO check the kill-switch/dry-run state as its
FIRST guard, before any other logic. killSwitchScope is a new
REQUIRED field on LoopReentryCandidate, checked via the identical
isMinerKillSwitchActive helper submission-gate.ts's shouldSubmit now
consults (the "single shared helper, not duplicated per call site"
this issue asks for) -- not a bespoke wrapper.

Threads killSwitchScope through packages/gittensory-miner/lib/loop-
reentry.js's attemptLoopReentry (validated fail-closed alongside its
existing repoFullName/outcome checks) and into the audit event payload
for traceability.
…#2339, #2336)

submission-gate.ts had zero root-vitest (Codecov-visible) coverage -- only an
engine-level node:test file, which Codecov cannot see. #2339's new kill-switch
check (2 lines) was the first change to this file Codecov could measure, and it
correctly flagged 0% patch coverage as a result.

Ports the existing, already-comprehensive engine-level test suite (18 scenarios:
kill-switch x3, pass/pass, fail/pass, pass/fail, both-fail, null-verdict,
null-slop, both-null, observe-mode x2, isSlopBandWithinThreshold x4, barrel
check) into a new root vitest file importing directly from engine source, giving
100% statement/branch/function/line coverage on the whole file, not just the
newly-changed lines.

Also resolves the rebase onto current main: an add/add conflict on
test/unit/miner-loop-reentry.test.ts (this branch's own commit history still
referenced an orphaned, pre-fix copy of the now-merged #2338 commit) and the
shared build-script line conflict (package.json), both cleanly reconciled with
no functional change to either.
…ing loop's handoff signal (#2337) (#5054)

* chore(orb): bump manifest target version to 0.4.1 (#5075)

orb-v0.4.0 was cut stable earlier today (2026-07-11), but the manifest
was never bumped afterward, so orb-beta-release's due-check correctly
refused to cut another 0.4.0-suffixed beta -- blocking the automated
beta channel for every feat/fix merged since, including #5071/#5072.

* feat(miner-governor): propagate the kill-switch into shouldSubmit (submission-gate.ts)

#2339's own deliverable: the manage-phase submission-gate function
must check the kill-switch/dry-run state as its FIRST guard, before
any other logic -- flipping the kill-switch must halt any pending
submission-gate check immediately, the same way it already halts the
Governor chokepoint (#2340).

killSwitchScope is a new REQUIRED field on SubmissionGateCandidate
(not optional-with-a-permissive-default) so a caller cannot forget to
resolve and pass it. Reuses isMinerKillSwitchActive (kill-switch.ts,
#2341) directly -- the single shared helper #2339 asks for, not a
bespoke wrapper -- rather than duplicating the check; the sibling
loop-reentry-policy.ts change (next commit) consults the identical
function.

* feat(miner-governor): propagate the kill-switch into shouldReenter (loop-reentry-policy.ts)

The sibling half of #2339's own deliverable: the manage-phase loop-
reentry function must ALSO check the kill-switch/dry-run state as its
FIRST guard, before any other logic. killSwitchScope is a new
REQUIRED field on LoopReentryCandidate, checked via the identical
isMinerKillSwitchActive helper submission-gate.ts's shouldSubmit now
consults (the "single shared helper, not duplicated per call site"
this issue asks for) -- not a bespoke wrapper.

Threads killSwitchScope through packages/gittensory-miner/lib/loop-
reentry.js's attemptLoopReentry (validated fail-closed alongside its
existing repoFullName/outcome checks) and into the audit event payload
for traceability.

* feat(miner-governor): wire the gated-submission trigger into the driving loop's handoff signal

Adds evaluateHarnessSubmissionTrigger (packages/gittensory-engine/src/
miner/harness-submission-trigger.ts) + evaluateAndRecordHarnessSubmis
sionTrigger (packages/gittensory-miner/lib/harness-submission-
trigger.js): connects shouldSubmit (#2336) to the actual driving
loop's own handoff signal -- iterate-loop.ts's (#2333) HandoffPacket,
produced the moment a real run's self-review reaches a clean
predicted-gate pass. Not wired into any scheduler as part of this
change, per the issue's own "manual owner sign-off on the wiring
before this ships to any default-on profile" deliverable.

Adds the session-level circuit breaker this issue's own deliverable
calls for, distinct from shouldSubmit's per-candidate signal checks:
N consecutive allow:false decisions in one session pauses the run
entirely pending human review (checked FIRST, before ever consulting
shouldSubmit -- once tripped, only a human clearing the session's own
tally can un-trip it, unlike a per-candidate block a later, different
candidate can clear on its own merits). Session-scoped rather than
per-repo, matching "pauses the run entirely" -- distinct from #2338's
loop-reentry circuit breaker, which is deliberately per-repo.

Every decision (allow or block) is appended to the local event ledger
with full reasons, whether or not the circuit breaker was involved.

This does NOT build or invoke buildOpenPrSpec (src/mcp/local-write-
tools.ts) -- that lives in the private root src/ tree, unreachable
from this portable package for the same cross-package-boundary reason
self-review-adapter.ts's slop injection exists. A real call site
(root-side integration, wired in a later issue) consults this
function's allow:true before it builds that spec itself.

On the referenced "harness driving loop": the issue cites both the
old M4-era gittensory-mcp harness adapter (#781, a different package)
and this session's own submission-gate.ts. Wired to iterate-loop.ts's
HandoffPacket instead, since that's the actual, current, same-wave
driving loop #2336 was built to compose with.

Test-covered per the issue's own explicit deliverable: a full
candidate -> gate-check -> submit (or correctly-blocked) cycle, plus
the circuit breaker tripping and resetting, and fail-closed validation.

* feat(miner-governor): thread killSwitchScope through the harness submission trigger

#2339 makes SubmissionGateCandidate.killSwitchScope required on
shouldSubmit -- this is the coupling fixup: evaluateHarnessSubmission
Trigger (#2337) constructs a shouldSubmit candidate internally, so it
now requires and forwards killSwitchScope from its own candidate
rather than duplicating the kill-switch check itself (shouldSubmit's
own guard covers it whenever the session circuit breaker hasn't
already short-circuited first).

Also closes a real, pre-existing codecov gap discovered while
verifying this: submission-gate.ts had ZERO root-vitest scenarios for
its null-predictedGateVerdict, null-slopAssessment, slop-exceeds-
threshold, and observe-mode-would-have-blocked/would-have-allowed
paths -- it's only reachable from the root vitest suite through this
file's own integration test, and that test never exercised them.
Added all five; submission-gate.ts now measures 100% branch coverage
via the actual codecov-visible path, not just the engine's own
node:test suite.

* fix(miner-governor): add missing as-never casts for deliberately-invalid deps assertions

Two "fails closed on malformed candidate" assertions in
miner-harness-submission-trigger.test.ts passed a HarnessSubmissionCandidateInput
missing slopThreshold/mode without the as-never cast the four other deliberately-
invalid-shape assertions in the same test already use, so tsc rejected them once
the type was fully threaded through. No behavior change -- the runtime fail-closed
assertions themselves were always correct.

Also carries forward the same stacked-branch rebase reconciliation as the two
branches this one is built on (rerere replayed the loop-reentry.test.ts
resolution automatically).
@JSONbored
JSONbored merged commit a21a8ad into main Jul 11, 2026
14 of 15 checks passed
@JSONbored
JSONbored deleted the feat/miner-killswitch-manage-loop-2339 branch July 11, 2026 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. manual-review Gittensor contributor context

Development

Successfully merging this pull request may close these issues.

maintainer: kill-switch propagation into the manage/loop subsystem

1 participant