You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The miner-chat redesign decided v1 is action-capable, not read-only-then-expand: chat will eventually be able to trigger discover/attempt, portfolio release/requeue, and governor pause/resume. That's safe specifically because every one of those writes already executes locally, under the miner's own credentials, gated by an existing fail-closed safety system — packages/loopover-engine/src/governor/chokepoint.ts:6-8,144-365 is the precedence ladder (global/per-repo kill-switch → dry-run → rate-limit → budget/turn/termination cap → non-convergence → self-reputation-throttle → self-plagiarism → allow) that any thrown stage fails closed on rather than falling through (chokepoint.ts:19-20), packages/loopover-miner/lib/governor-chokepoint.js:34-55 is the stateful wrapper that persists ledger events and advances rate-limit buckets around it (already called by attempt-runner.js:184-211's self-plagiarism check), and pretooluse-hook.js:1-16 wires the same deny logic into the Claude Agent SDK's PreToolUse hook, which the SDK guarantees runs even under bypassPermissions. packages/loopover-engine/src/miner/local-write-tools.ts:1-4,17-18 confirms LoopOver's backend never performs a write itself — it only ever builds an action spec that the miner's own local harness executes with the miner's own GitHub credentials. A chat input in front of controls that already exist as buttons (apps/loopover-miner-ui/src/lib/portfolio-queue-actions.ts → POST /api/portfolio-queue/{release,requeue}; the governor-control section of apps/loopover-miner-ui/src/routes/ledgers.tsx → POST /api/governor/{pause,resume}) creates no new privilege, provided every chat-triggered call goes through the exact same chokepoint/deny-hook path — never a parallel or bypass route.
This issue is shared scaffolding only: a config flag (off by default, flippable per-install), an allowlisted action-request schema/registry, and a dispatch function that is the single entry point any chat-issued action must go through. It does not wire up any concrete action family — that's three separate child issues (the discover/attempt action-dispatch child issue, the portfolio release/requeue action-dispatch child issue, and the governor pause/resume action-dispatch child issue), all of which depend on this one landing first.
This is contributor-eligible now, unlike the still-open design questions in #6208 (identity linkage/data path/weighting undecided), #6209 (detection mechanism/allowlist/auth model undecided), or the maintainer-chat issue #6230 itself (audience/backend/placement/cost model explicitly "not yet decided") — this issue's design is fully closed: exact action families, exact endpoints, exact safety mechanism, exact rollout gate. There is no "what should this look like" question left for a contributor to resolve, only "build exactly this."
⚠️ Read this before starting. This issue is scaffolding only. It must not register or implement discover, attempt, portfolio.release, portfolio.requeue, governor.pause, or governor.resume — those belong to the three child issues listed in Links & Resources, not here. It must not add any new HTTP route or vite-*-api.ts middleware — the dispatch function is a plain library entry point, not a wired endpoint, in this issue. It must not modify packages/loopover-engine/src/governor/chokepoint.ts, packages/loopover-miner/lib/governor-chokepoint.js, or pretooluse-hook.js — this issue only calls into them, unchanged. New code goes under packages/loopover-miner/lib/ only, following the existing module convention in that directory (see local-store.js, governor-chokepoint.js, portfolio-queue.js) — not under apps/loopover-miner-ui/, packages/loopover-engine/, or src/. A PR that wires any concrete action family, adds a new API route, or edits the chokepoint files directly does NOT resolve this issue and will be closed.
Requirements
New code lives in exactly two new files under packages/loopover-miner/lib/: chat-action-dispatch.js (the dispatch function) and chat-action-registry.js (the allowlist schema/registration API). No other production files are created or modified.
The config flag must default to OFF/disabled whenever it is unset, empty, or set to anything other than an explicit enable value — fail closed, never fail open.
The dispatch function must check the config flag first, before touching the registry or validating params. When disabled, it must return/throw a clearly-typed "disabled" result and must not look up or invoke any handler.
chat-action-registry.js must ship in this issue with zero registered actions. No discover, attempt, portfolio.release, portfolio.requeue, governor.pause, or governor.resume entries are added here.
Every action registered through chat-action-registry.js must supply its own params-validator function as part of registration; the dispatch function must run that validator before invoking the handler and must reject the request (not silently coerce or drop fields) on validation failure.
The registration API's handler contract must make it structurally impossible to register a handler that performs a write directly — a registered handler must be produced by (or wrapped through) the existing packages/loopover-miner/lib/governor-chokepoint.js wrapper (the same one attempt-runner.js:184-211 already calls) for actions the dispatch layer itself invokes, and the existing pretooluse-hook.jsPreToolUse wiring remains the enforcement layer for actions the coding agent performs directly — this issue does not add a second, competing safety check.
Add a unit test proving that attempting to register a raw (unwrapped) handler fails at registration time, not silently succeeds.
Add a short header comment in chat-action-dispatch.js stating the "single entry point, never bypass" contract and naming packages/loopover-engine/src/governor/chokepoint.ts and packages/loopover-miner/lib/governor-chokepoint.js by path, so the three child issues have an unambiguous starting point.
Do not touch apps/loopover-miner-ui/** in this issue — no route, no UI, no client fetcher. The chat backend and the three action-family issues are the only future consumers of this module.
Deliverables
packages/loopover-miner/lib/chat-action-dispatch.js — exports the single dispatch entry point (e.g. dispatchChatAction(request)); fail-closed config-flag gate; unknown-action rejection; runs the registered params-validator before invoking a handler.
packages/loopover-miner/lib/chat-action-registry.js — exports the allowlist registry and a registerChatAction(name, { paramsValidator, handler })-style API whose handler contract requires routing through governor-chokepoint.js; ships with zero actions registered.
Unit tests co-located next to the two new files (matching whichever test convention already sits beside governor-chokepoint.js/portfolio-queue.js in the same directory), covering: flag-off short-circuit, unknown-action rejection, params-validator pass/fail, and the handler-must-be-wrapped enforcement (both the accepted-wrapped case and the rejected-unwrapped case).
A regression-style test asserting the registry starts empty (guards against a future PR accidentally pre-registering an action family in this same module).
Test Coverage Requirements
Both new files live under packages/loopover-miner/lib/, which is outside src/** — per this repo's Codecov config, only src/** is patch-coverage gated, so these lines will not appear in the codecov/patch check and Codecov's 99% gate does not apply to them directly. That does not lower the bar: cover every branch by hand anyway — the flag-off short-circuit, the flag-on-but-unknown-action rejection, both params-validator branches, and both handler-wrapping branches (wrapped-accepted / unwrapped-rejected) — plus the empty-registry regression test. Confirm and follow whichever test runner packages/loopover-miner/lib/ already uses for its sibling files rather than introducing a second one for this package. Regardless, run the full local gate before opening the PR: npm run test:ci, npm audit --audit-level=moderate, and npm run test:coverage measured unsharded (other src/** files remain gated even though this issue's own two files aren't).
Expected Outcome
A disabled-by-default, single-entry-point chat-action dispatch layer exists in packages/loopover-miner/lib/, ready for the three action-family issues to register handlers into. Nothing about any existing dashboard button, CLI command, or MCP tool changes — the flag is off and the registry is empty, so no new action can execute yet. Once the three child issues each register their handler, it will be structurally impossible for any of them to bypass the Governor chokepoint/PreToolUse path, because the registration contract itself blocks an unwrapped handler rather than relying on review discipline to catch it.
Links & Resources
Safety precedent this dispatch layer must route through, unmodified: packages/loopover-engine/src/governor/chokepoint.ts:6-8,19-20,144-365 (the fail-closed precedence ladder); packages/loopover-miner/lib/governor-chokepoint.js:34-55 (the stateful wrapper, already called by attempt-runner.js:184-211's self-plagiarism check); pretooluse-hook.js:1-16 (the SDK-level PreToolUse enforcement layer for agent-driven actions).
Local-write boundary confirming this adds no new privilege: packages/loopover-engine/src/miner/local-write-tools.ts:1-4,17-18.
Existing button-backed endpoints the three child issues will eventually route through this dispatch layer (not touched by this issue): apps/loopover-miner-ui/src/lib/portfolio-queue-actions.ts (→ POST /api/portfolio-queue/{release,requeue}), the governor-control section of apps/loopover-miner-ui/src/routes/ledgers.tsx (→ POST /api/governor/{pause,resume}), and discover-cli.js/attempt-cli.js (CLI-only today, no HTTP mirror yet — the discover/attempt child issue adds that).
Module-naming precedent to follow: packages/loopover-miner/lib/local-store.js:1-29, the DRY'd path-resolution helper already shared by all 17 sqlite3 stores in this package — same "one small focused module per concern" convention this issue's two new files should follow.
Read-only surface this issue is independent of and must not regress: packages/loopover-miner/bin/loopover-miner-mcp.js's 11 loopover_miner_* tools remain the read-only half of chat's eventual tool surface.
Child issues that depend on this one (do not start until this issue merges): the discover/attempt action-dispatch child issue, the portfolio release/requeue action-dispatch child issue, and the governor pause/resume action-dispatch child issue.
Context
The miner-chat redesign decided v1 is action-capable, not read-only-then-expand: chat will eventually be able to trigger
discover/attempt, portfolio release/requeue, and governor pause/resume. That's safe specifically because every one of those writes already executes locally, under the miner's own credentials, gated by an existing fail-closed safety system —packages/loopover-engine/src/governor/chokepoint.ts:6-8,144-365is the precedence ladder (global/per-repo kill-switch → dry-run → rate-limit → budget/turn/termination cap → non-convergence → self-reputation-throttle → self-plagiarism → allow) that any thrown stage fails closed on rather than falling through (chokepoint.ts:19-20),packages/loopover-miner/lib/governor-chokepoint.js:34-55is the stateful wrapper that persists ledger events and advances rate-limit buckets around it (already called byattempt-runner.js:184-211's self-plagiarism check), andpretooluse-hook.js:1-16wires the same deny logic into the Claude Agent SDK'sPreToolUsehook, which the SDK guarantees runs even underbypassPermissions.packages/loopover-engine/src/miner/local-write-tools.ts:1-4,17-18confirms LoopOver's backend never performs a write itself — it only ever builds an action spec that the miner's own local harness executes with the miner's own GitHub credentials. A chat input in front of controls that already exist as buttons (apps/loopover-miner-ui/src/lib/portfolio-queue-actions.ts→POST /api/portfolio-queue/{release,requeue}; the governor-control section ofapps/loopover-miner-ui/src/routes/ledgers.tsx→POST /api/governor/{pause,resume}) creates no new privilege, provided every chat-triggered call goes through the exact same chokepoint/deny-hook path — never a parallel or bypass route.This issue is shared scaffolding only: a config flag (off by default, flippable per-install), an allowlisted action-request schema/registry, and a dispatch function that is the single entry point any chat-issued action must go through. It does not wire up any concrete action family — that's three separate child issues (the discover/attempt action-dispatch child issue, the portfolio release/requeue action-dispatch child issue, and the governor pause/resume action-dispatch child issue), all of which depend on this one landing first.
This is contributor-eligible now, unlike the still-open design questions in #6208 (identity linkage/data path/weighting undecided), #6209 (detection mechanism/allowlist/auth model undecided), or the maintainer-chat issue #6230 itself (audience/backend/placement/cost model explicitly "not yet decided") — this issue's design is fully closed: exact action families, exact endpoints, exact safety mechanism, exact rollout gate. There is no "what should this look like" question left for a contributor to resolve, only "build exactly this."
Requirements
packages/loopover-miner/lib/:chat-action-dispatch.js(the dispatch function) andchat-action-registry.js(the allowlist schema/registration API). No other production files are created or modified.chat-action-registry.jsmust ship in this issue with zero registered actions. Nodiscover,attempt,portfolio.release,portfolio.requeue,governor.pause, orgovernor.resumeentries are added here.chat-action-registry.jsmust supply its own params-validator function as part of registration; the dispatch function must run that validator before invoking the handler and must reject the request (not silently coerce or drop fields) on validation failure.packages/loopover-miner/lib/governor-chokepoint.jswrapper (the same oneattempt-runner.js:184-211already calls) for actions the dispatch layer itself invokes, and the existingpretooluse-hook.jsPreToolUsewiring remains the enforcement layer for actions the coding agent performs directly — this issue does not add a second, competing safety check.chat-action-dispatch.jsstating the "single entry point, never bypass" contract and namingpackages/loopover-engine/src/governor/chokepoint.tsandpackages/loopover-miner/lib/governor-chokepoint.jsby path, so the three child issues have an unambiguous starting point.apps/loopover-miner-ui/**in this issue — no route, no UI, no client fetcher. The chat backend and the three action-family issues are the only future consumers of this module.Deliverables
packages/loopover-miner/lib/chat-action-dispatch.js— exports the single dispatch entry point (e.g.dispatchChatAction(request)); fail-closed config-flag gate; unknown-action rejection; runs the registered params-validator before invoking a handler.packages/loopover-miner/lib/chat-action-registry.js— exports the allowlist registry and aregisterChatAction(name, { paramsValidator, handler })-style API whose handler contract requires routing throughgovernor-chokepoint.js; ships with zero actions registered.governor-chokepoint.js/portfolio-queue.jsin the same directory), covering: flag-off short-circuit, unknown-action rejection, params-validator pass/fail, and the handler-must-be-wrapped enforcement (both the accepted-wrapped case and the rejected-unwrapped case).Test Coverage Requirements
Both new files live under
packages/loopover-miner/lib/, which is outsidesrc/**— per this repo's Codecov config, onlysrc/**is patch-coverage gated, so these lines will not appear in thecodecov/patchcheck and Codecov's 99% gate does not apply to them directly. That does not lower the bar: cover every branch by hand anyway — the flag-off short-circuit, the flag-on-but-unknown-action rejection, both params-validator branches, and both handler-wrapping branches (wrapped-accepted / unwrapped-rejected) — plus the empty-registry regression test. Confirm and follow whichever test runnerpackages/loopover-miner/lib/already uses for its sibling files rather than introducing a second one for this package. Regardless, run the full local gate before opening the PR:npm run test:ci,npm audit --audit-level=moderate, andnpm run test:coveragemeasured unsharded (othersrc/**files remain gated even though this issue's own two files aren't).Expected Outcome
A disabled-by-default, single-entry-point chat-action dispatch layer exists in
packages/loopover-miner/lib/, ready for the three action-family issues to register handlers into. Nothing about any existing dashboard button, CLI command, or MCP tool changes — the flag is off and the registry is empty, so no new action can execute yet. Once the three child issues each register their handler, it will be structurally impossible for any of them to bypass the Governor chokepoint/PreToolUsepath, because the registration contract itself blocks an unwrapped handler rather than relying on review discipline to catch it.Links & Resources
packages/loopover-engine/src/governor/chokepoint.ts:6-8,19-20,144-365(the fail-closed precedence ladder);packages/loopover-miner/lib/governor-chokepoint.js:34-55(the stateful wrapper, already called byattempt-runner.js:184-211's self-plagiarism check);pretooluse-hook.js:1-16(the SDK-levelPreToolUseenforcement layer for agent-driven actions).packages/loopover-engine/src/miner/local-write-tools.ts:1-4,17-18.apps/loopover-miner-ui/src/lib/portfolio-queue-actions.ts(→POST /api/portfolio-queue/{release,requeue}), the governor-control section ofapps/loopover-miner-ui/src/routes/ledgers.tsx(→POST /api/governor/{pause,resume}), anddiscover-cli.js/attempt-cli.js(CLI-only today, no HTTP mirror yet — the discover/attempt child issue adds that).packages/loopover-miner/lib/local-store.js:1-29, the DRY'd path-resolution helper already shared by all 17 sqlite3 stores in this package — same "one small focused module per concern" convention this issue's two new files should follow.packages/loopover-miner/bin/loopover-miner-mcp.js's 11loopover_miner_*tools remain the read-only half of chat's eventual tool surface.docs(ui): audit ui-kit for existing chat-adjacent UI primitives) — audits the read-only/UI side of miner chat and the still-open maintainer-chat issue Spec: conversational chat interface for loopover (Lovable/Cursor-style) #6230, whose separate scope this issue explicitly does not touch.