Skip to content

feat(miner): wire chat action-dispatch to the existing portfolio release/requeue routes - #6850

Merged
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
luciferlive112116:feat/chat-portfolio-actions-6838
Jul 17, 2026
Merged

feat(miner): wire chat action-dispatch to the existing portfolio release/requeue routes#6850
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
luciferlive112116:feat/chat-portfolio-actions-6838

Conversation

@luciferlive112116

Copy link
Copy Markdown
Contributor

Summary

The chat action-dispatch scaffolding (#6519) ships its registry deliberately emptychat-action-registry.js:98 says so explicitly, and names this issue's family as one of the three that fill it. This registers the portfolio pair:

  • portfolio_release → the miner-ui client releasePortfolioQueueItem
  • portfolio_requeue → the miner-ui client requeuePortfolioQueueItem

Both clients POST the existing /api/portfolio-queue/{release,requeue} routes the dashboard buttons already call. No new route, no parallel write path, no direct store access — the clients are injected by the miner-ui wire module, so this module can't reach portfolio-queue.js or hand-roll a fetch even by accident. It owns only the registration contract + params validators, exactly like its chat-governor-actions.js sibling.

The one judgement call — why an allow-stage gate, not the chokepoint

This is the part worth reviewing, because "chat action doesn't route through the Governor chokepoint" reads like a bypass. It isn't, and I checked rather than assumed:

vite-portfolio-queue-actions-api.ts:4-6 describes itself as "a thin bridge to the EXISTING store methods the CLI's queue release / queue requeue subcommands already use (reclaimStuckItem / requeueItem) — no new queue semantics are invented here." I grepped it: it invokes no chokepoint at all. Release/requeue is local queue administration — it writes no content to GitHub.

So routing the chat path through evaluateGovernorChokepointGate would gate chat more strictly than the button sitting beside it, which is a change to the button-triggered flow's contract that #6838 explicitly freezes ("No changes to the existing route or button-triggered flow"). Instead this mirrors chat-governor-actions.js exactly, which faced the identical situation for administrative pause/resume and resolved it the same way: satisfy the registry's governorGatedHandler brand with an allow-stage evaluateGate, and keep execution behind the shared LOOPOVER_MINER_CHAT_ACTIONS flag via dispatchChatAction.

The safety properties that matter are all still structural, not by convention:

  • register() refuses any handler not produced by governorGatedHandler() (private Symbol brand, unforgeable), so a raw function can never be registered.
  • The flag is checked first, before the registry is touched or params are validated — fail closed.
  • evaluateGate stays injectable, so a future decision to route these through the real chokepoint is a one-argument change here, not a rewrite.

Params validation

Both actions share isPortfolioItemChatParams. Two decisions differ from the governor sibling, deliberately:

  • Params are required. Governor pause/resume accept nullish (there's a sensible no-arg default). There is no sensible default queue item, so nullish must not resolve to one.
  • Unknown keys are rejected, not ignored. These params can be model-authored. A typo'd or hallucinated key must fail loudly rather than silently act on a different item than intended — {...item, status: "done"} and {...item, repo_full_name: …} are both pinned as rejected.

apiBaseUrl stays optional (the client's own type is Pick<…, "repoFullName" | "identifier" | "apiBaseUrl"> and the CLI path omits it), and when absent it is omitted from the forwarded object rather than passed as an explicit undefined — otherwise it would serialize into the POST body and change the request the buttons already send. A test pins the exact key list, because toHaveBeenCalledWith uses toEqual semantics and would treat {a, b: undefined} as equal to {a} — it cannot catch this on its own.

Validation

  • Patch coverage 100%, measured from the v8 JSON report — the file is new, so every line is patch: 34/34 statements, 36/36 branches, 6/6 functions, zero partials. Clears the 99% codecov/patch wall on packages/loopover-miner/lib/** (root vitest.config.ts includes packages/loopover-miner/lib/**/*.js, so this file is gated).
    • The last partial branch was options.registry ?? chatActionRegistry — the shared-registry default that production actually uses, which every other test skips by injecting a registry. It now has its own test.
  • 16/16 new tests pass; the 3 sibling chat suites (registry, dispatch, governor-actions) stay green at 35/35.
  • npm run typecheck0 errors, matching clean main exactly. (tsc caught two real errors in my own test that vitest didn't: untyped vi.fn(async () => …) infers a zero-length tuple, so mock.calls[0][0] wouldn't type-check. Mocks are now parameter-typed.)
  • npm run build:miner — the new file is added to the miner package's explicit node --check list, beside its chat-governor-actions.js sibling; package.json re-validated as parseable JSON.
  • eslint — 0 errors/0 warnings · git diff --check clean · rebased on latest main, no base conflict.

One pre-existing failure, not mine — verified, not assumed: check-miner-package.test.ts fails 1/13 identically on clean main with my work stashed.

Scope

  • Four files: the module + its .d.ts, the package's build list, and the root test suite. Wanted paths (packages/, test/).
  • No UI: this is the registration layer, so there is no visual change and no screenshot table applies.
  • chat-action-registry.js, chat-action-dispatch.js, chat-governor-actions.js, the portfolio route, and the miner-ui clients are all untouched.
  • No secrets; no changelog, site/, CNAME, or lovable changes.

Safety

  • Off by default: LOOPOVER_MINER_CHAT_ACTIONS gates execution, and dispatchChatAction checks it before anything else. With the flag unset, behavior is byte-identical to today.
  • Registration is idempotent (registry.has guarded), so double-wiring can't throw on an already-registered name.
  • Chat reaches exactly the endpoint the button reaches — one auditable path, which is the epic's stated design.

Closes #6838

…ase/requeue routes (JSONbored#6838)

Registers portfolio_release / portfolio_requeue into the chat-action registry the
scaffolding (JSONbored#6519) ships empty. Handlers call the miner-ui clients
releasePortfolioQueueItem / requeuePortfolioQueueItem, so chat POSTs the same
/api/portfolio-queue/{release,requeue} routes the dashboard buttons already use --
no new route, no parallel write path, no direct store access.

Release/requeue is local queue administration, not a chokepoint content-write: the
route is a thin bridge to the same store methods the CLI's queue release/requeue use
and invokes no chokepoint itself. Gating chat more strictly than the button beside it
would change the button-triggered flow JSONbored#6838 freezes, so this mirrors
chat-governor-actions.js exactly and satisfies the registry brand with an allow-stage
evaluateGate. Execution stays behind the shared LOOPOVER_MINER_CHAT_ACTIONS flag.

Closes JSONbored#6838
@superagent-security

Copy link
Copy Markdown
Contributor

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

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.64%. Comparing base (b9e39dd) to head (783b5ec).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6850   +/-   ##
=======================================
  Coverage   93.63%   93.64%           
=======================================
  Files         683      684    +1     
  Lines       68163    68191   +28     
  Branches    18706    18706           
=======================================
+ Hits        63827    63855   +28     
  Misses       3350     3350           
  Partials      986      986           
Flag Coverage Δ
shard-1 43.27% <100.00%> (-0.42%) ⬇️
shard-2 36.96% <0.00%> (+0.23%) ⬆️
shard-3 32.57% <0.00%> (-0.19%) ⬇️
shard-4 34.44% <0.00%> (-0.37%) ⬇️
shard-5 31.31% <0.00%> (-0.48%) ⬇️
shard-6 46.02% <0.00%> (+0.55%) ⬆️

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

Files with missing lines Coverage Δ
...kages/loopover-miner/lib/chat-portfolio-actions.js 100.00% <100.00%> (ø)

@loopover-orb loopover-orb Bot added the gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. label Jul 17, 2026
@loopover-orb

loopover-orb Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-17 08:31:55 UTC

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

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This PR wires two chat actions (portfolio_release/portfolio_requeue) into the existing chat-action registry, mirroring chat-governor-actions.js's pattern of an allow-stage gate for local queue administration that doesn't touch the chokepoint. The implementation is clean, idempotent, validates params strictly (rejecting unknown keys, empty strings, non-strings), and correctly forwards only the injected miner-ui clients rather than reaching portfolio-queue.js directly. Tests exercise both the direct registration API and the full dispatchChatAction path including flag-off, validation-failure, and gate-deny cases, matching production wiring via the shared registry fallback test.

Nits — 5 non-blocking
  • packages/loopover-miner/lib/chat-portfolio-actions.js:38 — `isPortfolioItemChatParams` loops over `Object.keys` and does per-field checks inline; consider extracting the shape check into a small allowlist Set to reduce the reported complexity/nesting, though it reads fine as-is.
  • The description states this closes/relates to Miner dashboard chat: portfolio action-dispatch #6838 but I can't verify against the actual issue text from the diff alone — confirm the issue link is present on the PR since that's a hard requirement for external contributor PRs.
  • packages/loopover-miner/lib/chat-portfolio-actions.js — the file-level comment block is unusually long (17 lines) for a straightforward registration module; the reasoning is valuable but could live in the PR description rather than as a permanent code comment, per repo comment-density conventions.
  • Consider whether `isPortfolioItemChatParams` and its sibling in chat-governor-actions.js could share a small `hasOnlyKeys` helper to avoid duplicating the unknown-key-rejection loop pattern across future chat-action modules.
  • No migration/schema concerns here since this is pure application wiring with no schema changes — good scope discipline matching the single-issue-family constraint.

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 #6838
Related work ⚠️ 3 scoped overlaps Top overlaps are listed below; lower-confidence bulk is hidden.
Change scope ❌ 8/20 High 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: 163 registered-repo PR(s), 99 merged, 30 issue(s).
Contributor context ✅ Confirmed Gittensor contributor luciferlive112116; Gittensor profile; 163 PR(s), 30 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: moderate
Linked issue satisfaction

Addressed
The PR adds chat-portfolio-actions.js registering portfolio_release/portfolio_requeue that are explicitly wired to injected releaseItem/requeueItem clients (the existing miner-ui clients hitting /api/portfolio-queue/{release,requeue}), with no new route or direct store access, matching the issue's core wiring requirement and including substantial regression test coverage.

Review context
  • Author: luciferlive112116
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, JavaScript, MDX, Rust, TypeScript
  • Official Gittensor activity: 163 PR(s), 30 issue(s).
  • Related work: Titles/paths share 9 meaningful terms. (issue #6836, issue #6838)
  • Related work: Titles/paths share 9 meaningful terms. (issue #6837, issue #6838)
  • Related work: Titles/paths share 9 meaningful terms. (issue #6839, issue #6838)
  • Additional title-only matches omitted; title-only overlap does not block.
Contributor next steps
  • Start here: Review top overlaps.
  • Then work through the remaining 2 steps in the Signals table above.
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.

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.

Miner dashboard chat: portfolio action-dispatch

1 participant