diff --git a/packages/loopover-miner/DEPLOYMENT.md b/packages/loopover-miner/DEPLOYMENT.md index ea9ff90027..40e2ffd6d8 100644 --- a/packages/loopover-miner/DEPLOYMENT.md +++ b/packages/loopover-miner/DEPLOYMENT.md @@ -186,6 +186,24 @@ See [`docs/operations-runbook.md`](docs/operations-runbook.md) for operational s See [`docs/sizing.md`](docs/sizing.md) for measured CPU/RAM/disk numbers for laptop mode vs. fleet mode at different worker counts, with the exact commands used to reproduce them. +## Environment variable reference + +All optional. The per-store `LOOPOVER_MINER__DB` path overrides (e.g. `LOOPOVER_MINER_PORTFOLIO_QUEUE_DB`) +and `LOOPOVER_MINER_CONFIG_DIR` are covered above under the fleet/state notes; the rest are: + +| Variable | Read by | Purpose | +| --- | --- | --- | +| `LOOPOVER_MINER_AMS_POLICY_PATH` | `lib/ams-policy.js` | Path to the operator's `.loopover-ams.yml` policy spec. | +| `LOOPOVER_MINER_AMS_COLLECTOR_URL`, `LOOPOVER_MINER_AMS_COLLECTOR_TOKEN` | `lib/orb-export.js` | Endpoint + bearer for pushing fleet state to an AMS/Orb collector. | +| `LOOPOVER_MINER_CHAT_ACTIONS` | `lib/chat-action-dispatch.js` | Truthy string enables the chat-action-dispatch flag (off by default). | +| `LOOPOVER_MINER_LEDGER_RETENTION_DAYS`, `LOOPOVER_MINER_LEDGER_RETENTION_MAX_ROWS` | `lib/store-maintenance.js` | Opt-in ledger retention window / row cap. | +| `LOOPOVER_MINER_LOG_LEVEL` | `lib/logger.js` | Log verbosity override. | +| `LOOPOVER_MINER_NO_UPDATE_CHECK` | `lib/update-check.js` | Truthy string disables the background update check. | +| `LOOPOVER_MINER_REPO_CLONE_DIR` | `lib/repo-clone.js` | Base directory for cloned target repos. | +| `LOOPOVER_MINER_WORKTREE_DIR` | `lib/worktree-allocator.js` | Base directory for per-attempt git worktrees. | +| `LOOPOVER_MINER_SENTRY_DSN`, `LOOPOVER_MINER_SENTRY_ENVIRONMENT` | `lib/sentry.js` | Optional Sentry error reporting (DSN + environment tag). | +| `LOOPOVER_MINER_VERSION` | `lib/version.js` | Overrides the reported release id (e.g. for a custom build). | + ## Optional hosted discovery plane (opt-in) The Phase 6 **hosted discovery-index** is **off by default** — unlike Orb fleet export (`ORB_AIR_GAP` is the only opt-out). Operators who want cross-fleet metadata queries or soft-claim coordination must opt in explicitly. See [`docs/discovery-plane-operator-guide.md`](docs/discovery-plane-operator-guide.md) ([#4309](https://github.com/JSONbored/gittensory/issues/4309), placeholder until [#4300](https://github.com/JSONbored/gittensory/issues/4300) / [#4301](https://github.com/JSONbored/gittensory/issues/4301) / [#4302](https://github.com/JSONbored/gittensory/issues/4302) ship). diff --git a/packages/loopover-miner/lib/deployment-docs-audit.d.ts b/packages/loopover-miner/lib/deployment-docs-audit.d.ts index a2d4a3474c..94206f82a2 100644 --- a/packages/loopover-miner/lib/deployment-docs-audit.d.ts +++ b/packages/loopover-miner/lib/deployment-docs-audit.d.ts @@ -8,6 +8,9 @@ export type DeploymentDocsClaims = { /** Filesystem-independent view of the live source tree the parsed claims are checked against. */ export type DeploymentDocsReality = { hasEnvRead: (name: string) => boolean; + /** The enumerable set of every real env-var read, so the reverse audit direction (#6601) can diff a real + * `LOOPOVER_MINER_*` read that DEPLOYMENT.md never documents, not just probe one documented name at a time. */ + envReads: Iterable; pathExists: (relativePath: string) => boolean; isRegisteredCommand: (name: string) => boolean; }; diff --git a/packages/loopover-miner/lib/deployment-docs-audit.js b/packages/loopover-miner/lib/deployment-docs-audit.js index 49d5b0686e..88b754f4e3 100644 --- a/packages/loopover-miner/lib/deployment-docs-audit.js +++ b/packages/loopover-miner/lib/deployment-docs-audit.js @@ -102,6 +102,18 @@ export function auditDeploymentDocs(claims, reality) { ); } } + // Reverse direction (#6601): a real `LOOPOVER_MINER_*` env-var read that DEPLOYMENT.md never documents. Scoped + // to the `LOOPOVER_MINER_` prefix and excluding the `*_DB` family (documented generically via one pattern + // sentence, not enumerated) and the bare `MINER_*` alias namespace (which also matches non-env event/metric/ + // filename constants). `reality.envReads` is the enumerable set of real reads the forward `hasEnvRead` probes. + const documented = new Set(claims.envVars); + for (const name of reality.envReads) { + if (name.startsWith("LOOPOVER_MINER_") && !name.endsWith("_DB") && !documented.has(name)) { + failures.push( + `env var "${name}" is read under packages/loopover-miner/** but is not documented in DEPLOYMENT.md`, + ); + } + } return { ok: failures.length === 0, failures }; } diff --git a/scripts/check-miner-deployment-docs.d.mts b/scripts/check-miner-deployment-docs.d.mts index a6af3a3fa8..40ac0e7f9a 100644 --- a/scripts/check-miner-deployment-docs.d.mts +++ b/scripts/check-miner-deployment-docs.d.mts @@ -1,5 +1,7 @@ export type MinerDeploymentReality = { hasEnvRead: (name: string) => boolean; + /** Enumerable set of every real env-var read, for the reverse audit direction (#6601). */ + envReads: Iterable; pathExists: (relativePath: string) => boolean; isRegisteredCommand: (name: string) => boolean; }; diff --git a/scripts/check-miner-deployment-docs.mjs b/scripts/check-miner-deployment-docs.mjs index 7941e7da2c..018642d712 100644 --- a/scripts/check-miner-deployment-docs.mjs +++ b/scripts/check-miner-deployment-docs.mjs @@ -39,6 +39,9 @@ export function buildLiveMinerDeploymentReality() { const registered = scanRegisteredCommands(readFileSync(BIN_ENTRY, "utf8")); return { hasEnvRead: (name) => envReads.has(name), + // The full enumerable read-set (#6601), so auditDeploymentDocs can diff the reverse direction — a real + // `LOOPOVER_MINER_*` read missing from DEPLOYMENT.md — not just probe one documented name at a time. + envReads, pathExists: (relativePath) => existsSync(resolve(MINER_DIR, relativePath)), isRegisteredCommand: (name) => registered.has(name), }; diff --git a/test/unit/check-miner-deployment-docs.test.ts b/test/unit/check-miner-deployment-docs.test.ts index 31fb99953d..251501ed5b 100644 --- a/test/unit/check-miner-deployment-docs.test.ts +++ b/test/unit/check-miner-deployment-docs.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import { + buildLiveMinerDeploymentReality, main, runMinerDeploymentDocsAudit, } from "../../scripts/check-miner-deployment-docs.mjs"; @@ -14,6 +15,16 @@ describe("check-miner-deployment-docs (#6158)", () => { expect(result.claimCounts.subcommands).toBeGreaterThan(0); }); + it("exposes the live env-var read set as an enumerable field for the reverse audit (#6601)", () => { + const reality = buildLiveMinerDeploymentReality(); + const reads = [...reality.envReads]; + // Populated, and includes a var the reverse check now requires DEPLOYMENT.md to document. + expect(reads.length).toBeGreaterThan(0); + expect(reads).toContain("LOOPOVER_MINER_LOG_LEVEL"); + // The enumerable set and the boolean probe agree. + expect(reads.every((name) => reality.hasEnvRead(name))).toBe(true); + }); + it("fails when env-var backing reads are forced missing (drift fixture)", () => { expect(() => runMinerDeploymentDocsAudit({ testMode: "missing-env" })).toThrow(/DEPLOYMENT\.md is out of sync/i); }); diff --git a/test/unit/miner-deployment-docs-audit.test.ts b/test/unit/miner-deployment-docs-audit.test.ts index d11104d524..bccdd947c1 100644 --- a/test/unit/miner-deployment-docs-audit.test.ts +++ b/test/unit/miner-deployment-docs-audit.test.ts @@ -43,6 +43,7 @@ function buildLiveReality(): DeploymentDocsReality { const registered = scanRegisteredCommands(readFileSync(BIN_ENTRY, "utf8")); return { hasEnvRead: (name) => envReads.has(name), + envReads, pathExists: (relativePath) => existsSync(resolve(MINER_DIR, relativePath)), isRegisteredCommand: (name) => registered.has(name), }; @@ -50,6 +51,7 @@ function buildLiveReality(): DeploymentDocsReality { const ALWAYS_IN_SYNC: DeploymentDocsReality = { hasEnvRead: () => true, + envReads: [], pathExists: () => true, isRegisteredCommand: () => true, }; @@ -184,11 +186,46 @@ describe("loopover-miner DEPLOYMENT.md docs-accuracy audit (#5180)", () => { expect(result.failures[0]).toContain("not registered"); }); + it("reverse (#6601): flags a real LOOPOVER_MINER_* read that DEPLOYMENT.md never documents", () => { + const result = auditDeploymentDocs( + { envVars: [], filePaths: [], subcommands: [] }, + { ...ALWAYS_IN_SYNC, envReads: ["LOOPOVER_MINER_LOG_LEVEL"] }, + ); + expect(result.ok).toBe(false); + expect(result.failures).toHaveLength(1); + expect(result.failures[0]).toContain("LOOPOVER_MINER_LOG_LEVEL"); + expect(result.failures[0]).toContain("is not documented"); + }); + + it("reverse (#6601): does NOT flag a real read that IS documented", () => { + const result = auditDeploymentDocs( + { envVars: ["LOOPOVER_MINER_LOG_LEVEL"], filePaths: [], subcommands: [] }, + { ...ALWAYS_IN_SYNC, envReads: ["LOOPOVER_MINER_LOG_LEVEL"] }, + ); + expect(result).toEqual({ ok: true, failures: [] }); + }); + + it("reverse (#6601): does NOT flag an undocumented LOOPOVER_MINER_*_DB read (generic-pattern exemption)", () => { + const result = auditDeploymentDocs( + { envVars: [], filePaths: [], subcommands: [] }, + { ...ALWAYS_IN_SYNC, envReads: ["LOOPOVER_MINER_PORTFOLIO_QUEUE_DB"] }, + ); + expect(result).toEqual({ ok: true, failures: [] }); + }); + + it("reverse (#6601): does NOT flag an undocumented bare MINER_* read (no LOOPOVER_MINER_ prefix)", () => { + const result = auditDeploymentDocs( + { envVars: [], filePaths: [], subcommands: [] }, + { ...ALWAYS_IN_SYNC, envReads: ["MINER_PR_OUTCOME_EVENT"] }, + ); + expect(result).toEqual({ ok: true, failures: [] }); + }); + it("assertDeploymentDocsInSync throws and names every stale claim at once", () => { expect(() => assertDeploymentDocsInSync( { envVars: ["LOOPOVER_MINER_GONE"], filePaths: ["gone.yml"], subcommands: ["gone"] }, - { hasEnvRead: () => false, pathExists: () => false, isRegisteredCommand: () => false }, + { hasEnvRead: () => false, envReads: [], pathExists: () => false, isRegisteredCommand: () => false }, ), ).toThrow(/LOOPOVER_MINER_GONE[\s\S]*gone\.yml[\s\S]*loopover-miner gone/); });