Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/loopover-miner/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,23 @@ For provider selection and the CLI-specific model/timeout overrides, see
`LOOPOVER_MINER_PORTFOLIO_QUEUE_DB` — to relocate an individual file. `doctor`'s `store-integrity:*` checks
report the persistent stores, so it is the quickest way to confirm what exists and is readable on disk.

**Other operator env knobs** (catalog also in [`docs/env-reference.md`](docs/env-reference.md)):

- `LOOPOVER_MINER_LOG_LEVEL` — log verbosity (`debug` / `info` / `warn` / `error`; also `--verbose` / `--quiet`).
- `LOOPOVER_MINER_NO_UPDATE_CHECK` — set to `1`/`true`/`yes` to skip the startup npm-registry version nudge
(also `--no-update-check`).
- `LOOPOVER_MINER_VERSION` — override the resolved release id (fleet image builds stamp this).
- `LOOPOVER_MINER_REPO_CLONE_DIR` — base directory for cloned evaluation repos (`lib/repo-clone.js`).
- `LOOPOVER_MINER_WORKTREE_DIR` — base directory for per-attempt git worktrees (`lib/worktree-allocator.js`).
- `LOOPOVER_MINER_LEDGER_RETENTION_DAYS` / `LOOPOVER_MINER_LEDGER_RETENTION_MAX_ROWS` — opt-in age- and/or
size-based pruning for the append-only event/governor/prediction ledgers (unset ⇒ retention off).
- `LOOPOVER_MINER_CHAT_ACTIONS` — flag that enables chat-action dispatch (`lib/chat-action-dispatch.js`).
- `LOOPOVER_MINER_AMS_POLICY_PATH` — override path for the local AMS policy file (`lib/ams-policy.js`).
- `LOOPOVER_MINER_AMS_COLLECTOR_URL` / `LOOPOVER_MINER_AMS_COLLECTOR_TOKEN` — Orb export collector endpoint
and optional bearer credential (`lib/orb-export.js`).
- `LOOPOVER_MINER_SENTRY_DSN` / `LOOPOVER_MINER_SENTRY_ENVIRONMENT` — opt-in Sentry error tracking (no-op
unless the DSN is set; environment defaults to `production`).

4. Optional per-repo miner goals: copy [`.loopover-miner.yml.example`](../../.loopover-miner.yml.example) to a target repo as `.loopover-miner.yml`. See [`docs/miner-goal-spec.md`](docs/miner-goal-spec.md).

## Fleet mode walkthrough
Expand Down
2 changes: 2 additions & 0 deletions packages/loopover-miner/lib/deployment-docs-audit.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export type DeploymentDocsClaims = {
/** Filesystem-independent view of the live source tree the parsed claims are checked against. */
export type DeploymentDocsReality = {
hasEnvRead: (name: string) => boolean;
/** Every scanned LOOPOVER_MINER_* / MINER_* token from miner+engine source (#6601 reverse check). */
envReads: Iterable<string>;
pathExists: (relativePath: string) => boolean;
isRegisteredCommand: (name: string) => boolean;
};
Expand Down
32 changes: 25 additions & 7 deletions packages/loopover-miner/lib/deployment-docs-audit.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Docs-accuracy audit for the miner's DEPLOYMENT.md (#5180). Mirrors the self-host docs audit
// Docs-accuracy audit for the miner's DEPLOYMENT.md (#5180, #6601). Mirrors the self-host docs audit
// (apps/loopover-ui/src/lib/selfhost-docs-audit.ts): parse the deployment doc, then assert every
// LOOPOVER_MINER_* / MINER_* env var, repo-relative file path, and `loopover-miner <subcommand>`
// it documents still exists under packages/loopover-miner/**. A rename or move that leaves the doc
// it documents still exists under packages/loopover-miner/** — and the reverse for non-_DB
// LOOPOVER_MINER_* reads that the doc never mentions (#6601). A rename or move that leaves the doc
// stale then fails CI with a message naming the exact stale claim, instead of misleading operators.
// Wired into CI via `npm run test:miner-deployment-docs-audit` (scripts/check-miner-deployment-docs.mjs)
// and the live unit suite in test/unit/miner-deployment-docs-audit.test.ts (#6158).
Expand Down Expand Up @@ -75,11 +76,19 @@ export function scanRegisteredCommands(binSource) {
}

/**
* Cross-check parsed DEPLOYMENT.md claims against reality. `reality` supplies three predicates so this
* comparison stays pure and filesystem-independent: `hasEnvRead(name)` (a read of that env var exists
* under packages/loopover-miner/**), `pathExists(relativePath)` (the doc-relative path is on disk),
* and `isRegisteredCommand(name)` (the subcommand is dispatched by the CLI). Returns the drift findings,
* each failure naming the specific stale claim rather than a generic mismatch.
* Cross-check parsed DEPLOYMENT.md claims against reality. `reality` supplies three predicates plus
* an enumerable `envReads` set so this comparison stays pure and filesystem-independent:
* `hasEnvRead(name)` (a read of that env var exists under packages/loopover-miner/**),
* `envReads` (every scanned LOOPOVER_MINER_* / MINER_* token — used for the reverse check in #6601),
* `pathExists(relativePath)` (the doc-relative path is on disk), and `isRegisteredCommand(name)`
* (the subcommand is dispatched by the CLI). Returns the drift findings, each failure naming the
* specific stale claim rather than a generic mismatch.
*
* Reverse env-var check (#6601): every `LOOPOVER_MINER_*` token in `envReads` that does NOT end in
* `_DB` and is absent from `claims.envVars` is a failure. Tokens ending in `_DB` are exempt because
* DEPLOYMENT.md documents that family generically via `LOOPOVER_MINER_<NAME>_DB`. Bare `MINER_*`
* aliases (no `LOOPOVER_MINER_` prefix) are not included in the reverse direction — only the forward
* "documented claim must have a real read" check applies to them.
*/
export function auditDeploymentDocs(claims, reality) {
const failures = [];
Expand All @@ -90,6 +99,15 @@ export function auditDeploymentDocs(claims, reality) {
);
}
}
const documentedEnvVars = new Set(claims.envVars);
for (const name of reality.envReads) {
if (!name.startsWith("LOOPOVER_MINER_")) continue;
if (name.endsWith("_DB")) continue;
if (documentedEnvVars.has(name)) continue;
failures.push(
`env var "${name}" is read under packages/loopover-miner/** but is not documented in DEPLOYMENT.md`,
);
}
for (const path of claims.filePaths) {
if (!reality.pathExists(path)) {
failures.push(`file path "${path}" is linked from DEPLOYMENT.md but no longer exists on disk`);
Expand Down
2 changes: 2 additions & 0 deletions scripts/check-miner-deployment-docs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export function buildLiveMinerDeploymentReality() {
const registered = scanRegisteredCommands(readFileSync(BIN_ENTRY, "utf8"));
return {
hasEnvRead: (name) => envReads.has(name),
// Enumerable copy of the same scan `hasEnvRead` queries — reverse undocumented-env check (#6601).
envReads,
pathExists: (relativePath) => existsSync(resolve(MINER_DIR, relativePath)),
isRegisteredCommand: (name) => registered.has(name),
};
Expand Down
13 changes: 13 additions & 0 deletions test/unit/check-miner-deployment-docs.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import {
buildLiveMinerDeploymentReality,
main,
runMinerDeploymentDocsAudit,
} from "../../scripts/check-miner-deployment-docs.mjs";
Expand All @@ -14,6 +15,18 @@ describe("check-miner-deployment-docs (#6158)", () => {
expect(result.claimCounts.subcommands).toBeGreaterThan(0);
});

it("populates envReads on live reality and the full bidirectional audit passes (#6601)", () => {
const reality = buildLiveMinerDeploymentReality();
const envReads = [...reality.envReads];
expect(envReads.length).toBeGreaterThan(0);
expect(envReads.some((name) => name.startsWith("LOOPOVER_MINER_"))).toBe(true);
expect(typeof reality.hasEnvRead).toBe("function");
// Same reality object the CI script builds — full audit (forward + reverse) must stay green.
const result = runMinerDeploymentDocsAudit({ reality });
expect(result.ok).toBe(true);
expect(result.failures).toEqual([]);
});

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);
});
Expand Down
52 changes: 51 additions & 1 deletion test/unit/miner-deployment-docs-audit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ 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),
};
}

const ALWAYS_IN_SYNC: DeploymentDocsReality = {
hasEnvRead: () => true,
envReads: [],
pathExists: () => true,
isRegisteredCommand: () => true,
};
Expand Down Expand Up @@ -162,6 +164,49 @@ describe("loopover-miner DEPLOYMENT.md docs-accuracy audit (#5180)", () => {
expect(result.failures[0]).toContain("no read");
});

it("flags an undocumented non-_DB LOOPOVER_MINER_* real read by name (#6601)", () => {
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("not documented");
});

it("does not flag a documented LOOPOVER_MINER_* real read (#6601)", () => {
const result = auditDeploymentDocs(
{ envVars: ["LOOPOVER_MINER_LOG_LEVEL"], filePaths: [], subcommands: [] },
{
...ALWAYS_IN_SYNC,
hasEnvRead: (name) => name === "LOOPOVER_MINER_LOG_LEVEL",
envReads: ["LOOPOVER_MINER_LOG_LEVEL"],
},
);
expect(result).toEqual({ ok: true, failures: [] });
});

it("does not flag an undocumented LOOPOVER_MINER_*_DB token (generic-pattern exemption) (#6601)", () => {
// DEPLOYMENT.md covers the per-store family via LOOPOVER_MINER_<NAME>_DB — reverse check must not
// force exhaustive per-store enumeration of that already-documented pattern.
const result = auditDeploymentDocs(
{ envVars: [], filePaths: [], subcommands: [] },
{ ...ALWAYS_IN_SYNC, envReads: ["LOOPOVER_MINER_PORTFOLIO_QUEUE_DB"] },
);
expect(result).toEqual({ ok: true, failures: [] });
});

it("does not flag an undocumented bare MINER_* token without LOOPOVER_MINER_ prefix (#6601)", () => {
// Bare MINER_* matches non-env identifiers (event types, metrics, filenames); reverse check scopes
// to LOOPOVER_MINER_* only. Forward direction (documented MINER_* must have a real read) is unchanged.
const result = auditDeploymentDocs(
{ envVars: [], filePaths: [], subcommands: [] },
{ ...ALWAYS_IN_SYNC, envReads: ["MINER_PR_OUTCOME_EVENT"] },
);
expect(result).toEqual({ ok: true, failures: [] });
});

it("flags a documented file path that no longer exists on disk", () => {
const result = auditDeploymentDocs(
{ envVars: [], filePaths: ["docker-compose.moved.yml"], subcommands: [] },
Expand All @@ -188,7 +233,12 @@ describe("loopover-miner DEPLOYMENT.md docs-accuracy audit (#5180)", () => {
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/);
});
Expand Down
Loading