From 41ec8ab0eee1760cb90403d48a01dea2d94c520d Mon Sep 17 00:00:00 2001 From: oktofeesh1 <287075021+oktofeesh1@users.noreply.github.com> Date: Thu, 16 Jul 2026 06:09:16 +0200 Subject: [PATCH] docs(ams): port docs/config-precedence.md to a website docs page (docs.ams-config-precedence.tsx) Add content/docs/ams-config-precedence.mdx covering AMS's per-concern configuration layering (per-repo goal spec, operator env, CLI flags, operator policy file) for kill switch, governor live mode, coding-agent execution mode, forge credential resolution, GitHub token resolution, and local store paths -- rendered via the existing DocsPage/Callout/CodeBlock/ FeatureRow primitives. Add the page to docs-nav.tsx's "AMS: deployment" subgroup and to docs.index.tsx's Maintainers audience card. packages/loopover-miner/docs/config-precedence.md stays as the canonical source (ships inside the published @loopover/miner package) with a short pointer added to the new website page. --- .../content/docs/ams-config-precedence.mdx | 186 ++++++++++++++++++ .../src/components/site/docs-nav.tsx | 1 + apps/loopover-ui/src/routeTree.gen.ts | 21 ++ .../src/routes/docs.ams-config-precedence.tsx | 49 +++++ apps/loopover-ui/src/routes/docs.index.tsx | 1 + .../loopover-miner/docs/config-precedence.md | 4 + 6 files changed, 262 insertions(+) create mode 100644 apps/loopover-ui/content/docs/ams-config-precedence.mdx create mode 100644 apps/loopover-ui/src/routes/docs.ams-config-precedence.tsx diff --git a/apps/loopover-ui/content/docs/ams-config-precedence.mdx b/apps/loopover-ui/content/docs/ams-config-precedence.mdx new file mode 100644 index 0000000000..b86de459f5 --- /dev/null +++ b/apps/loopover-ui/content/docs/ams-config-precedence.mdx @@ -0,0 +1,186 @@ +--- +title: Miner config precedence +description: How AMS layers configuration across per-repo goal spec, operator env, CLI flags, and operator policy files -- the order each concern actually implements today. +--- + +AMS does **not** have a single `config.js` resolver. Configuration is layered by concern across +several modules under `packages/loopover-miner/lib/` and `@loopover/engine`. This document states +the order each layer **actually implements today** — not an idealized or corrected order. + +## Configuration layers + + ... argv, scoped to one invocation. Modules: lib/attempt-cli.js, lib/discover-cli.js, lib/loop-cli.js, and others.", + }, + { + title: "Operator file (not goal spec)", + description: + "~/.config/loopover-miner/.loopover-ams.yml, operator execution policy. Module: lib/ams-policy.js.", + }, + ]} +/> + +`.loopover-miner.yml` is **maintainer-authored in the target repo**. Operator env and CLI flags +are **never overridden by a target repo's goal spec** for operator-owned policy (see +`lib/ams-policy.js`'s header). + +## .loopover-miner.yml file discovery + +First existing file wins (engine `MINER_GOAL_SPEC_FILENAMES`): + +**1.** `.loopover-miner.yml` + +**2.** `.github/loopover-miner.yml` + +**3.** `.loopover-miner.json` + +**4.** `.github/loopover-miner.json` + +## Precedence by concern + +### Kill switch (halt miner writes) + +**Sources:** `LOOPOVER_MINER_KILL_SWITCH` (operator env) and `.loopover-miner.yml` → +`killSwitch.paused`. + +**Order (safest wins, engine `resolveMinerKillSwitch`):** + +**1.** Global env halt → scope `"global"` (always reported even when the repo yml also pauses). + +**2.** Else per-repo yml `killSwitch.paused: true` → scope `"repo"`. + +**3.** Else → scope `"none"`. + + + There is **no CLI flag** for kill-switch today. `MINER_CODING_AGENT_PAUSED` is a separate axis + (coding-agent spawn only) and does not change kill-switch scope. + + +### Governor live write mode + +**Sources:** `LOOPOVER_MINER_LIVE_MODE=live` (operator env) and `.loopover-miner.yml` → +`execution.liveModeOptIn: live`. + +**Order (engine `resolveMinerActionMode`):** + +**1.** Kill switch active → `"paused"` (overrides any live opt-in). + +**2.** Else **both** operator env **and** repo yml must equal the exact string `"live"` → +`"live"`. + +**3.** Else → `"dry_run"`. + + + This is an **AND** requirement, not "last writer wins". Either side missing or malformed → + dry-run. There is also **no CLI flag wired to governor live mode** today — + `attempt --live` / `loop --live` affect coding-agent spawn mode only (below). + + +### Coding-agent execution mode (spawn the driver?) + +**Sources:** `MINER_CODING_AGENT_PAUSED` (operator env) and `attempt|loop --live` (CLI, per +invocation). + +**Order (engine `resolveCodingAgentExecutionMode`, wired in `lib/attempt-cli.js`):** + +**1.** Global env pause (`MINER_CODING_AGENT_PAUSED` truthy) → `"paused"`. + +**2.** Else CLI `--live` absent → `agentDryRun: true` → `"dry_run"` (`attempt-cli.js` enforces +dry-run default). + +**3.** Else CLI `--live` present → `"live"`. + + + There is no `.loopover-miner.yml` field for coding-agent mode today. + + +### Discover forge credential env var name + +**Sources:** `discover --token-env ` (CLI), programmatic `options.tokenEnv`, forge default +(`GITHUB_TOKEN`). + +**Order (`lib/discover-cli.js`):** + +**1.** CLI `--token-env` + +**2.** Else programmatic `options.tokenEnv` + +**3.** Else `resolveForgeConfig(...).tokenEnvVar` (default `GITHUB_TOKEN`) + + + There is no `.loopover-miner.yml` forge block today; `--api-base-url` follows the same CLI → + programmatic → default shape for the API host. + + +### GitHub token value + +**Sources:** `GITHUB_TOKEN` (operator env), a `loopover-mcp login` session recorded in the +`loopover-mcp` config file (`~/.config/loopover/config.json` by default), programmatic +`options.githubToken`. + +**Order (`lib/github-token-resolution.js`'s `resolveGitHubToken`, called once at the top of each +CLI entrypoint — `loop`, `attempt`, `init --verify-token`, `manage poll` — then threaded down +explicitly to every real GitHub caller):** + +**1.** Caller-supplied `options.githubToken` (an explicit override passed programmatically) wins +outright. + +**2.** Else `GITHUB_TOKEN` env — an existing self-host operator's PAT setup keeps working +unchanged, no filesystem or network access. + +**3.** Else a live token fetched from the authenticated `loopover-mcp login` session (`POST +/v1/auth/github/token`, cached in memory for the process's lifetime; a failed fetch is not +cached, so a later call retries rather than staying stuck). + +**4.** Else `null` — the caller's own existing "no token" failure mode applies (git operations +requiring auth fail the same way they did before this feature existed). + + + This is a distinct concern from "Discover forge credential env var name" above, which resolves + the *name* of an env var to read, not the token *value* itself; `discover --token-env` is + unaffected by this section. + + +### Local SQLite store paths + +**Sources:** per-store `LOOPOVER_MINER_*_DB` env var, then `LOOPOVER_MINER_CONFIG_DIR`, then XDG +default (`lib/local-store.js`). + +Explicit per-store env **wins** over config dir; config dir **wins** over XDG. No CLI or +goal-spec override. + +## Known gaps / inconsistencies + +- **No unified precedence** across yml + env + CLI for a single knob — each concern owns its own + resolver. +- **Live execution** spans two independent gates: coding-agent `--live` (spawn) vs governor + env+yml (writes). Both must allow live for a full live open-pr attempt. +- **Forge tenant overrides** (`--api-base-url`, `--token-env`) are CLI/programmatic only; + `.loopover-miner.yml` cannot set them yet. +- **Operator AMS policy** (`.loopover-ams.yml`) is separate from per-repo goal spec; goal spec + never overrides operator policy. + + + If a future change adds yml or CLI for a setting documented here as env-only, this page and + `test/unit/miner-config-precedence.test.ts` both need updating. + + +## See also + +- [`packages/loopover-miner/docs/miner-goal-spec.md`](https://github.com/JSONbored/loopover/blob/main/packages/loopover-miner/docs/miner-goal-spec.md) — goal-spec field reference +- [`packages/loopover-miner/docs/env-reference.md`](https://github.com/JSONbored/loopover/blob/main/packages/loopover-miner/docs/env-reference.md) — generated operator env list +- ORB's `.loopover.yml` precedence (`yml > DB > defaults`) in [Self-hosting configuration](/docs/self-hosting-configuration) — analogous documentation style, different runtime diff --git a/apps/loopover-ui/src/components/site/docs-nav.tsx b/apps/loopover-ui/src/components/site/docs-nav.tsx index 06433fc79a..3c5683df22 100644 --- a/apps/loopover-ui/src/components/site/docs-nav.tsx +++ b/apps/loopover-ui/src/components/site/docs-nav.tsx @@ -80,6 +80,7 @@ export const docsNav: DocsGroup[] = [ { to: "/docs/ams-observability", label: "Observing your miner" }, { to: "/docs/ams-unattended-scheduling", label: "Unattended scheduling" }, { to: "/docs/ams-sizing", label: "Resource sizing" }, + { to: "/docs/ams-config-precedence", label: "Config precedence" }, ], }, ], diff --git a/apps/loopover-ui/src/routeTree.gen.ts b/apps/loopover-ui/src/routeTree.gen.ts index d01aa0b25d..bac1be6423 100644 --- a/apps/loopover-ui/src/routeTree.gen.ts +++ b/apps/loopover-ui/src/routeTree.gen.ts @@ -62,6 +62,7 @@ import { Route as DocsAmsSizingRouteImport } from './routes/docs.ams-sizing' import { Route as DocsAmsOperationsRunbookRouteImport } from './routes/docs.ams-operations-runbook' import { Route as DocsAmsObservabilityRouteImport } from './routes/docs.ams-observability' import { Route as DocsAmsDeploymentRouteImport } from './routes/docs.ams-deployment' +import { Route as DocsAmsConfigPrecedenceRouteImport } from './routes/docs.ams-config-precedence' import { Route as DocsAiSummariesRouteImport } from './routes/docs.ai-summaries' import { Route as AppWorkbenchRouteImport } from './routes/app.workbench' import { Route as AppRunsRouteImport } from './routes/app.runs' @@ -360,6 +361,11 @@ const DocsAmsDeploymentRoute = DocsAmsDeploymentRouteImport.update({ path: '/ams-deployment', getParentRoute: () => DocsRoute, } as any) +const DocsAmsConfigPrecedenceRoute = DocsAmsConfigPrecedenceRouteImport.update({ + id: '/ams-config-precedence', + path: '/ams-config-precedence', + getParentRoute: () => DocsRoute, +} as any) const DocsAiSummariesRoute = DocsAiSummariesRouteImport.update({ id: '/ai-summaries', path: '/ai-summaries', @@ -467,6 +473,7 @@ export interface FileRoutesByFullPath { '/app/runs': typeof AppRunsRoute '/app/workbench': typeof AppWorkbenchRoute '/docs/ai-summaries': typeof DocsAiSummariesRoute + '/docs/ams-config-precedence': typeof DocsAmsConfigPrecedenceRoute '/docs/ams-deployment': typeof DocsAmsDeploymentRoute '/docs/ams-observability': typeof DocsAmsObservabilityRoute '/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute @@ -535,6 +542,7 @@ export interface FileRoutesByTo { '/app/runs': typeof AppRunsRoute '/app/workbench': typeof AppWorkbenchRoute '/docs/ai-summaries': typeof DocsAiSummariesRoute + '/docs/ams-config-precedence': typeof DocsAmsConfigPrecedenceRoute '/docs/ams-deployment': typeof DocsAmsDeploymentRoute '/docs/ams-observability': typeof DocsAmsObservabilityRoute '/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute @@ -607,6 +615,7 @@ export interface FileRoutesById { '/app/runs': typeof AppRunsRoute '/app/workbench': typeof AppWorkbenchRoute '/docs/ai-summaries': typeof DocsAiSummariesRoute + '/docs/ams-config-precedence': typeof DocsAmsConfigPrecedenceRoute '/docs/ams-deployment': typeof DocsAmsDeploymentRoute '/docs/ams-observability': typeof DocsAmsObservabilityRoute '/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute @@ -680,6 +689,7 @@ export interface FileRouteTypes { | '/app/runs' | '/app/workbench' | '/docs/ai-summaries' + | '/docs/ams-config-precedence' | '/docs/ams-deployment' | '/docs/ams-observability' | '/docs/ams-operations-runbook' @@ -748,6 +758,7 @@ export interface FileRouteTypes { | '/app/runs' | '/app/workbench' | '/docs/ai-summaries' + | '/docs/ams-config-precedence' | '/docs/ams-deployment' | '/docs/ams-observability' | '/docs/ams-operations-runbook' @@ -819,6 +830,7 @@ export interface FileRouteTypes { | '/app/runs' | '/app/workbench' | '/docs/ai-summaries' + | '/docs/ams-config-precedence' | '/docs/ams-deployment' | '/docs/ams-observability' | '/docs/ams-operations-runbook' @@ -1252,6 +1264,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DocsAmsDeploymentRouteImport parentRoute: typeof DocsRoute } + '/docs/ams-config-precedence': { + id: '/docs/ams-config-precedence' + path: '/ams-config-precedence' + fullPath: '/docs/ams-config-precedence' + preLoaderRoute: typeof DocsAmsConfigPrecedenceRouteImport + parentRoute: typeof DocsRoute + } '/docs/ai-summaries': { id: '/docs/ai-summaries' path: '/ai-summaries' @@ -1417,6 +1436,7 @@ const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren) interface DocsRouteChildren { DocsAiSummariesRoute: typeof DocsAiSummariesRoute + DocsAmsConfigPrecedenceRoute: typeof DocsAmsConfigPrecedenceRoute DocsAmsDeploymentRoute: typeof DocsAmsDeploymentRoute DocsAmsObservabilityRoute: typeof DocsAmsObservabilityRoute DocsAmsOperationsRunbookRoute: typeof DocsAmsOperationsRunbookRoute @@ -1462,6 +1482,7 @@ interface DocsRouteChildren { const DocsRouteChildren: DocsRouteChildren = { DocsAiSummariesRoute: DocsAiSummariesRoute, + DocsAmsConfigPrecedenceRoute: DocsAmsConfigPrecedenceRoute, DocsAmsDeploymentRoute: DocsAmsDeploymentRoute, DocsAmsObservabilityRoute: DocsAmsObservabilityRoute, DocsAmsOperationsRunbookRoute: DocsAmsOperationsRunbookRoute, diff --git a/apps/loopover-ui/src/routes/docs.ams-config-precedence.tsx b/apps/loopover-ui/src/routes/docs.ams-config-precedence.tsx new file mode 100644 index 0000000000..73483c2eb9 --- /dev/null +++ b/apps/loopover-ui/src/routes/docs.ams-config-precedence.tsx @@ -0,0 +1,49 @@ +import { createFileRoute, notFound } from "@tanstack/react-router"; +import { Suspense } from "react"; + +import { DocsPage } from "@/components/site/docs-page"; +import { docsClientLoader } from "@/lib/docs-client-loader"; + +// Rendered from content/docs/ams-config-precedence.mdx via fumadocs-mdx's browser entry +// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow +// primitives -- not fumadocs-ui's bundled components. See docs-source.ts's comment +// for why the loader below resolves only a plain, serializable path string. +export const Route = createFileRoute("/docs/ams-config-precedence")({ + loader: async () => { + const { docsSource } = await import("@/lib/docs-source"); + const page = docsSource.getPage(["ams-config-precedence"]); + if (!page) throw notFound(); + return { path: page.path, title: page.data.title, description: page.data.description }; + }, + head: () => ({ + meta: [ + { title: "Miner config precedence — LoopOver docs" }, + { + name: "description", + content: + "How AMS layers configuration across per-repo goal spec, operator env, CLI flags, and operator policy files -- the order each concern actually implements today.", + }, + { property: "og:title", content: "Miner config precedence — LoopOver docs" }, + { + property: "og:description", + content: + "How AMS layers configuration across per-repo goal spec, operator env, CLI flags, and operator policy files -- the order each concern actually implements today.", + }, + { property: "og:url", content: "/docs/ams-config-precedence" }, + ], + links: [{ rel: "canonical", href: "/docs/ams-config-precedence" }], + }), + component: AmsConfigPrecedence, +}); + +function AmsConfigPrecedence() { + const { path, title, description } = Route.useLoaderData(); + const Content = docsClientLoader.getComponent(path); + return ( + + Loading…

}> + +
+
+ ); +} diff --git a/apps/loopover-ui/src/routes/docs.index.tsx b/apps/loopover-ui/src/routes/docs.index.tsx index 1bdecda313..a80c6012db 100644 --- a/apps/loopover-ui/src/routes/docs.index.tsx +++ b/apps/loopover-ui/src/routes/docs.index.tsx @@ -78,6 +78,7 @@ const AUDIENCES: Audience[] = [ { to: "/docs/ams-observability", label: "Observing your miner" }, { to: "/docs/ams-unattended-scheduling", label: "Unattended scheduling" }, { to: "/docs/ams-sizing", label: "Resource sizing" }, + { to: "/docs/ams-config-precedence", label: "Config precedence" }, { to: "/docs/self-hosting-docs-audit", label: "Self-host docs audit" }, { to: "/docs/maintainer-install-trust", label: "Install & trust guide" }, { to: "/docs/github-app", label: "GitHub App configuration" }, diff --git a/packages/loopover-miner/docs/config-precedence.md b/packages/loopover-miner/docs/config-precedence.md index 8f562e7ae6..3261476432 100644 --- a/packages/loopover-miner/docs/config-precedence.md +++ b/packages/loopover-miner/docs/config-precedence.md @@ -1,5 +1,9 @@ # Miner config precedence +> Also published on the docs website: [Miner config precedence](https://loopover.ai/docs/ams-config-precedence) +> (same content, rendered with search and the rest of the maintainer docs nav). This file remains +> the canonical source and ships inside the published `@loopover/miner` package. + AMS does **not** have a single `config.js` resolver. Configuration is layered by concern across several modules under `packages/loopover-miner/lib/` and `@loopover/engine`. This document states the order each layer **actually implements today** — not an idealized or corrected order. ## Configuration layers