Skip to content

feat(miner): add tenant create/list/destroy control-plane admin CLI - #7284

Merged
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
xfodev:feat/miner-tenant-cli-7275
Jul 19, 2026
Merged

feat(miner): add tenant create/list/destroy control-plane admin CLI#7284
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
xfodev:feat/miner-tenant-cli-7275

Conversation

@xfodev

@xfodev xfodev commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a loopover-miner tenant command group — create <name> [--product <p>], list, and destroy <name> — that provisions, lists, and tears down hosted tenant instances against the ORB+AMS hosting control-plane's provisioning API.

  • lib/tenant-client.js — a Bearer-authed HTTP client that mirrors discovery-index-client.js's env-gated, never-auto-enabled posture: completely inert unless LOOPOVER_MINER_CONTROL_PLANE is set and a URL is configured. Unlike that client's deliberately fail-open opportunistic supplement, tenant admin actions fail loud — a disabled / unconfigured / unreachable / non-2xx / malformed-body condition throws a clear Error that the CLI surfaces as a non-zero exit and message, never a silent degrade. Each call is a single bounded request (AbortSignal.timeout, no retry) so a non-idempotent create is never silently re-sent. Lifecycle state values are passed through exactly as the API reports them.
  • lib/tenant-cli.js — a thin argv-parse + dispatch layer (runTenantCreate / runTenantList / runTenantDestroy behind a runTenantCli dispatcher) over the client, with --json and human-readable output modes.
  • Wired tenant into the bin dispatch alongside orb export (the other network command), added help lines, and regenerated the env reference for the three new LOOPOVER_MINER_CONTROL_PLANE* vars.

The client is injectable (fetchImpl, env) and the CLI accepts injected client functions, so the whole surface is tested hermetically with no real control plane.

Scope

  • Narrow, one coherent change (a new opt-in CLI command group + its HTTP client)
  • In wantedPaths (packages/**, apps/loopover-ui/**, test/**); no blocked paths
  • New capability is flag-gated and OFF by default (LOOPOVER_MINER_CONTROL_PLANE)

Validation

  • typecheck (root tsc --noEmit) — clean
  • test:coverage for the new suite — 38 tests pass; 100% line + branch coverage on both new source files (verified against coverage-final.json)
  • Env reference regenerated + committed (npm run miner:env-reference); --check clean
  • Rebased onto upstream/main; git diff --name-only upstream/main shows only this change's files

Safety

  • No secrets, tokens, wallets, hotkeys/coldkeys, trust scores, or reward values in code, comments, tests, or PR text
  • The admin credential is read from an env var and only ever placed in an Authorization header — never logged, printed, or echoed
  • Fail-loud negative paths (disabled plane, missing URL/token, unreachable host, non-2xx, malformed body) are all covered by tests

Closes #7275

@xfodev
xfodev requested a review from JSONbored as a code owner July 19, 2026 12:32
@superagent-security

Copy link
Copy Markdown
Contributor

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

Add a `loopover-miner tenant` command group that provisions, lists, and
tears down hosted tenant instances against the ORB+AMS hosting
control-plane's provisioning API.

tenant-client.js is a Bearer-authed HTTP client that mirrors
discovery-index-client.js's env-gated, never-auto-enabled posture: it is
inert unless LOOPOVER_MINER_CONTROL_PLANE is set and a URL configured.
Unlike that client's fail-open opportunistic supplement, tenant admin
actions FAIL LOUD -- a disabled/unconfigured/unreachable/non-2xx/malformed
condition throws a clear error surfaced by the CLI as a non-zero exit,
never a silent degrade. Each call is a single bounded request (no retry),
so a non-idempotent create is never silently re-sent. Lifecycle states are
passed through exactly as the API reports them.

tenant-cli.js is a thin argv-parse + dispatch layer over the client;
tenant is wired into the bin dispatch alongside the other network command.
Regenerated the env reference for the three new LOOPOVER_MINER_CONTROL_PLANE*
vars.

Closes JSONbored#7275
@codecov

codecov Bot commented Jul 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.82%. Comparing base (2372739) to head (e9d05e9).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7284      +/-   ##
==========================================
+ Coverage   93.81%   93.82%   +0.01%     
==========================================
  Files         704      706       +2     
  Lines       69462    69576     +114     
  Branches    18896    18896              
==========================================
+ Hits        65167    65281     +114     
  Misses       3302     3302              
  Partials      993      993              
Flag Coverage Δ
shard-1 43.11% <0.00%> (-0.51%) ⬇️
shard-2 37.49% <100.00%> (+0.14%) ⬆️
shard-3 32.98% <0.00%> (-0.08%) ⬇️
shard-4 34.42% <0.00%> (+0.10%) ⬆️
shard-5 31.57% <0.00%> (-0.62%) ⬇️
shard-6 45.95% <0.00%> (+0.16%) ⬆️

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

Files with missing lines Coverage Δ
packages/loopover-miner/lib/cli.js 100.00% <ø> (ø)
packages/loopover-miner/lib/tenant-cli.js 100.00% <100.00%> (ø)
packages/loopover-miner/lib/tenant-client.js 100.00% <100.00%> (ø)

@xfodev
xfodev force-pushed the feat/miner-tenant-cli-7275 branch from fdf7136 to e9d05e9 Compare July 19, 2026 12:38
@loopover-orb loopover-orb Bot added the gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. label Jul 19, 2026
@loopover-orb

loopover-orb Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-19 12:46:08 UTC

10 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 new `loopover-miner tenant` CLI command group (create/list/destroy) that talks to a hosted control-plane provisioning API, following the same env-gated opt-in pattern established by discovery-index-client.js but intentionally failing loud instead of degrading silently, which is the right choice for non-idempotent admin actions like tenant create/destroy. The client (tenant-client.js) and CLI layer (tenant-cli.js) are cleanly separated, injectable for hermetic testing, and the accompanying test suite (393 lines) exercises every disabled/unconfigured/unreachable/non-2xx/malformed-body branch plus argv parsing edge cases. Wiring into bin/loopover-miner.js and the generated env-reference docs is consistent with existing conventions (orb export, discovery plane).

Nits — 5 non-blocking
  • packages/loopover-miner/lib/tenant-client.js:44-48 sequential single-purpose guard clauses (disabled/URL/token) register as 'deep nesting' by the external linter but read fine as written — no action needed unless the repo's actual lint config flags it.
  • The external brief's 'debug leftover console.*' flags on tenant-cli.js are false positives — console.log/console.error here are the CLI's intended human/JSON output mechanism, not leftover debugging.
  • packages/loopover-miner/lib/tenant-client.js:27 'Master opt-in' comment mirrors discovery-index-client.js's existing wording verbatim, so it's consistent with established convention in this codebase rather than a new choice to reconsider.
  • parseTenantCreateArgs (tenant-cli.js:16) rejects any `--product` value starting with `-`, which would wrongly reject a legitimately dash-prefixed product name — worth a one-line comment noting this is a deliberate simplification if intentional.
  • Consider documenting in tenant-client.js why `create` intentionally has no idempotency key / dedup guard beyond 'no retry', since a caller-side double-invocation (e.g., a flaky wrapper script) could still double-provision — out of scope for this client itself but worth a doc note.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #7275
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: 27 registered-repo PR(s), 16 merged, 0 issue(s).
Contributor context ✅ Confirmed Gittensor contributor xfodev; Gittensor profile; 27 PR(s), 0 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: moderate
Linked issue satisfaction

Addressed
The PR adds tenant-cli.js (create/list/destroy argv parsing + dispatch) and tenant-client.js (Bearer-authed, env-gated, fail-loud HTTP client mirroring discovery-index-client.js's pattern), wires it into bin/loopover-miner.js, passes through lifecycle state verbatim, and includes extensive regression tests covering success, malformed-response, non-2xx, and unreachable-host paths for every call.

Review context
  • Author: xfodev
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: JavaScript, TypeScript
  • Official Gittensor activity: 27 PR(s), 0 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Keep the PR focused and include validation evidence before maintainer review.
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.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 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 LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit 37ca8a5 into JSONbored:main Jul 19, 2026
16 checks passed
JSONbored added a commit that referenced this pull request Jul 26, 2026
…ck namespace (#8805) (#8813)

maybeCloseForContributorCapOnOpen executed its close under only the
per-(repo, author) cap lock — outside the "one shared lock namespace covering
every mutating PR pass" contract (transient-locks.ts) — so a sweep-fanned
agent-regate-pr job holding the PR-actuation lock could plan/execute a
different action for the SAME PR concurrently with the cap close.

The execution section now also try-claims the per-PR actuation lock, nested
author-outer → PR-inner. That is the OPPOSITE nesting of the executor's
pre-merge cap re-check (PR-outer → author-inner, #7284) — safe regardless:
both locks are non-blocking try-claims, so cross-order contention degrades to
both passes deferring cleanly (the end-of-pipeline cap check and the next
tick are the backstops), never a blocking-wait deadlock. Documented at the
claim site. Contention test mirrors the existing author-lock sibling: the
close defers (no PATCH), the normal pipeline falls through.
JSONbored added a commit that referenced this pull request Jul 26, 2026
…ibutor-cap-lock TTL (#9109)

* fix(orb): flush orphaned locks at boot, restore sweep candidacy on resume, and widen the contributor-cap-lock TTL

Three related lock/liveness fixes:

#9021 -- every Redis-backed lock (pr-actuation-lock, ai-review-lock,
contributor-cap-wake/-lock) survives a container restart with its TTL intact.
On this single-instance deployment, any lock present at boot is provably
orphaned -- the process that claimed it is gone. Left alone, each class
strands real work for its own TTL (30 min for ai-review-lock, #8998). Adds
flushOrphanedLocksAtBoot (selfhost/redis-cache.ts), a best-effort SCAN-delete
over the four exclusivity-lock prefixes, wired in at server boot before the
queue starts. Deliberately leaves delivery:*, pr-panel-retrigger-pending:*,
ci-pending-first-seen:*, and fresh-rebase-forced:* untouched -- none are
exclusivity locks, and each has its own restart-survival contract.

#9018 -- a paused repo's PRs can go green DURING the pause window (CI-
completion passes plan-and-suppress the whole time), and resuming performed no
catch-up: if a PR was ALSO regated once before the pause, agent-sweep.ts's
#never-endless-reregate rule permanently excludes it from future sweep
candidacy, stranding it silently. Adds clearPullRequestsRegatedAtForOpenPrs and
calls it on the paused->live transition, both from the single-repo MCP
pause/resume tool and the installation-wide bulk-settings route -- restoring
one-shot sweep candidacy for the repo's open PRs exactly once.

#9024 -- claimContributorCapLock's 30s TTL was sized only for the executor's
brief pre-merge recheck, but maybeCloseForContributorCapOnOpen holds the SAME
lock across a much longer body (token mint, live GitHub calls, label writes,
the nested pr-actuation-lock, a full executeAgentMaintenanceActions pass) that
can exceed 30s under GitHub rate-limit backoff -- reopening the exact #7284
TOCTOU this lock exists to close. Widened to 600s, matching claimPrActuationLock's
own TTL for a comparably long mutating body.

Closes #9018, #9021, #9024

Tests: 3 new flushOrphanedLocksAtBoot tests (deletes matching keys, returns 0
with nothing to do, fails open per-pattern on a scan error), a paused->live
MCP-tool test (clears open PRs' markers, never closed ones, never on pause or
a repeat resume), two bulk-settings-route tests (clears across all repos in an
installation; a non-agentPaused bulk change never touches the marker), and a
TTL-parity regression pinning claimContributorCapLock's TTL to
claimPrActuationLock's. 100% line+branch coverage on every changed line
(src/server.ts is Codecov's own documented ignore-listed entrypoint file).

* fix(ci): add the missing engine-build edge to @loopover/ui-miner#typecheck

The miner-UI typecheck reaches into packages/loopover-miner/lib/** (declared
as an input), and those files import @loopover/engine -- whose types resolve
to packages/loopover-engine/dist/index.d.ts. But unlike @loopover/ui#typecheck
(which declares the explicit @loopover/engine#build dependsOn edge for exactly
this reason), the miner-UI task only had ^build, and miner-ui has no
package.json dependency on engine to create that edge implicitly. Turbo
therefore ran engine build CONCURRENTLY with this typecheck; whenever the
engine cache missed AND the scheduler interleaved the two the wrong way, the
typecheck raced ahead of the dist emit and failed with dozens of phantom
"Cannot find module '@loopover/engine'" errors -- an intermittent, whole-job
validate-code failure with no real defect behind it (observed live on #9107's
first run and on main run 30214733191, while sibling runs of the identical
commit passed).
JSONbored added a commit that referenced this pull request Jul 27, 2026
…t just the re-check

The per-author contributor-cap mutex was released before the merge it
guards, reopening the #7284 TOCTOU: a concurrent cap-close could claim the
just-released lock, live-verify the merging PR as still open, and
wrong-close it for a cap the merge was about to relieve milliseconds
later. The lock now spans the actual merge mutation via a finally that
wraps performMutation, matching the sibling cap-close path.

The approval-queue accept path also builds its own contributorCapMergeRecheck
now, using the same resolvePerRepoContributorCapMatch the live webhook path
uses -- previously an accepted staged merge skipped the pre-merge re-check
entirely.

Closes #9159
JSONbored added a commit that referenced this pull request Jul 27, 2026
…t just the re-check

The per-author contributor-cap mutex was released before the merge it
guards, reopening the #7284 TOCTOU: a concurrent cap-close could claim the
just-released lock, live-verify the merging PR as still open, and
wrong-close it for a cap the merge was about to relieve milliseconds
later. The lock now spans the actual merge mutation via a finally that
wraps performMutation, matching the sibling cap-close path.

The approval-queue accept path also builds its own contributorCapMergeRecheck
now, using the same resolvePerRepoContributorCapMatch the live webhook path
uses -- previously an accepted staged merge skipped the pre-merge re-check
entirely.

Closes #9159
JSONbored added a commit that referenced this pull request Jul 27, 2026
…t just the re-check

The per-author contributor-cap mutex was released before the merge it
guards, reopening the #7284 TOCTOU: a concurrent cap-close could claim the
just-released lock, live-verify the merging PR as still open, and
wrong-close it for a cap the merge was about to relieve milliseconds
later. The lock now spans the actual merge mutation via a finally that
wraps performMutation, matching the sibling cap-close path.

The approval-queue accept path also builds its own contributorCapMergeRecheck
now, using the same resolvePerRepoContributorCapMatch the live webhook path
uses -- previously an accepted staged merge skipped the pre-merge re-check
entirely.

Closes #9159
JSONbored added a commit that referenced this pull request Jul 27, 2026
…-label integrity (#9235)

* fix(gate): store linked-issue claim time per (PR, issue) instead of per PR

The blended pull_requests.linked_issue_claimed_at column preserves an old
timestamp whenever the new linked-issue set overlaps the old one, so a PR
that claimed a throwaway issue on day one and later adds a valuable issue
inherits the day-one timestamp for the new issue too -- letting an old
placeholder PR backdate a claim and steal the duplicate-cluster winner slot.

Add a durable per-(repo, PR, issue) claim ledger (linked_issue_claims,
migration 0191) written once, immutably, the first time each issue is
observed linked to a PR. The duplicate-winner election now reads this
ledger, scoped to only the issue(s) actually contested with an open
sibling, instead of the PR-level blended value.

Closes #9160

* fix(gate): scope the duplicate-winner election to per-issue claim times

Wire the per-(PR, issue) claim ledger (previous commit) into every
duplicate-winner call site: resolveScopedLinkedIssueClaimedAt reads the
ledger's earliest claim among only the issue(s) actually contested with an
open sibling, and buildPullRequestAdvisory's election now compares against
that scoped value instead of the PR's blended linkedIssueClaimedAt column.
Falls back to the blended column when the caller doesn't resolve a scoped
value, so every unwired caller keeps prior behavior exactly.

Closes #9160

* fix(review): require reward-label opt-in and maintainer check for issue authors

A contributor who authored (or was assigned) the linked issue previously
inherited every label on it unconditionally, including reward-semantic
labels from an additive mapping -- bypassing the trustMaintainerAuthoredIssueForReward
opt-in and the maintainer-authorship check every other path already
requires. Split the unlock: author/assignee identity still unlocks a plain
type label unconditionally, but a reward-semantic label (named by any
mapping with removeOtherTypeLabels !== true) now always falls through to
the same opt-in + maintainer-check gate regardless of the author match.

Closes #9161

* fix(orb): make the label-vs-close correlation guard durable across entry points

The #label-close-split-brain guard only inspected the same-batch planned
array, so a rejected or breaker-downgraded close could leave its paired
enforcement label applied to a PR that stayed open. Two paths never carried
the pair through to the executor's own correlation check:

- downgradeCloseToHold now also drops any label whose closeKind matches a
  close it just downgraded (the breaker-downgrade half).
- The approval-queue accept path now re-derives pairing from the durable
  audit trail (the same agent.action.close record every execution entry
  point writes through) before applying a close-coupled label, since the
  queue stages one action per row and an independently rejected close has
  nothing correlating it to the label in the same batch.

Closes #9158

* fix(orb): hold the contributor-cap lock across the merge mutation, not just the re-check

The per-author contributor-cap mutex was released before the merge it
guards, reopening the #7284 TOCTOU: a concurrent cap-close could claim the
just-released lock, live-verify the merging PR as still open, and
wrong-close it for a cap the merge was about to relieve milliseconds
later. The lock now spans the actual merge mutation via a finally that
wraps performMutation, matching the sibling cap-close path.

The approval-queue accept path also builds its own contributorCapMergeRecheck
now, using the same resolvePerRepoContributorCapMatch the live webhook path
uses -- previously an accepted staged merge skipped the pre-merge re-check
entirely.

Closes #9159

* chore(cf): regenerate worker-configuration.d.ts after rebasing onto main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(miner): add loopover-miner tenant create/list/destroy admin CLI over the control-plane provisioning API

1 participant