From f378b1a553309746f064066589d9a475fb4d6a2a Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 30 Jul 2026 23:37:37 +0300 Subject: [PATCH] feat: CLI audit + local/api parity: open-gateway MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLI audit + local/api parity: open-gateway Repo: hasna/gateway (local: /home/hasna/workspace/hasna/opensource/open-gateway). Audit this repo's CLI: (1) Find every DEAD command — commands that are broken, error out, reference removed code, or are declared but non-functional — and fix them or remove them with justification. (2) Find every command that only works in 'local' mode (on-box SQLite/files) and does NOT work against the 'api' (self-hosted/remote) path; implement full api-mode support for each so every command works identically against local AND api backends (route through the cloud-router/stage-A/HTTP path per this repo's existing pattern). Ensure both modes are fully implemented and tested. Add regression tests for the fixed commands and for local-vs-api parity. Open a PR; do not merge. --- AMENDED 2026-07-30 (adversarial review). Read this part; it changes what "done" means. --- SCOPE CORRECTION. Part (2) assumes this repo has an HTTP/API backend. MANY REPOS IN THIS FAMILY DO NOT. Determine first whether this repo actually has a second backend (an HTTP client path, a `*_API_URL` / `*_API_KEY` config, a cloud-router or stage-A module). If it does NOT, then part (2) IS NOT APPLICABLE: say so explicitly in the PR body with the evidence you checked, and deliver part (1) alone. That is a COMPLETE and correct outcome for this task — not a failure, and not something to fake. REQUIRED ARTIFACT — a PR without it does not satisfy this task. The PR body MUST contain a table of EVERY command this CLI exposes, one row each, with a verdict: command | works locally? | works against the API backend (or N/A)? | dead? | changed in this PR? Derive the list from the CLI's own registration/parser, not from the README. This inventory is the deliverable that proves an audit happened. A PR that changes one or two commands WITHOUT the full inventory is a thin PR and does not close this task — a previous run in this family produced an 11-line diff with no inventory, which is exactly what this requirement exists to prevent. IF THE PREMISE IS ALREADY SATISFIED (no dead commands, parity already complete), that is a legitimate result: still publish the inventory table as the evidence, state the conclusion, and open the PR with docs/tests only, or report no-op with the table in the task comments. Do not invent a change to look productive. SIZE: if the full fix is too large for one PR, deliver the complete inventory plus the fixes you did make, and list explicitly in the PR body which commands remain and why. Partial-with-inventory beats complete-looking-without. IGNORE any `(local: /home/hasna/workspace/...)` path in the text above — it does not exist here. Work from the checkout you were given. X-Factory-Run: run_8828ecc7ea9f X-Factory-Task: a724f288-3244-42e5-8ec8-5d2c2252fc03 --- docs/cli-audit.md | 54 ++++++++++++++++++++++++++++++++++++++++++ tests/cli-help.test.ts | 37 +++++++++++++++++++++++++---- 2 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 docs/cli-audit.md diff --git a/docs/cli-audit.md b/docs/cli-audit.md new file mode 100644 index 0000000..6b20ecc --- /dev/null +++ b/docs/cli-audit.md @@ -0,0 +1,54 @@ +# CLI audit and backend parity + +## Summary + +- Audited every command token registered by `runCli` in `src/cli/index.ts`, including the implicit bare invocation that defaults to `help`. +- Found no dead, broken, removed-code, or non-functional commands. +- HTTP/API backend parity is not applicable because this CLI has no remote HTTP client backend. All config-aware commands load a local config path and call in-process modules. +- Added a regression test that pins the parser-derived command inventory and exercises all three help invocations. + +## API backend scope + +The repository ships an HTTP gateway server, but it does not ship a second HTTP/API backend for CLI operations: + +- `GatewayConfig` defines runtime, server, inbound auth, storage, policy, providers, models, routes, and budgets. It has no remote gateway API URL or remote CLI credential. +- `GatewayAuthConfig.apiKeyEnv` configures inbound authentication for the server; it is not a CLI HTTP-client credential. +- The CLI imports config, routing, budget, smoke, and server functions directly. Its config-aware handlers read `--config` from the local filesystem and invoke those modules in-process. +- Outbound HTTP calls in `src/providers` target configured model providers for inference and smoke checks. They are not an alternate backend for CLI state operations. +- `runtime.mode: "production-cloud"` hardens a deployed server. `storage.cloud` selects an in-process SQLite or PostgreSQL usage-ledger adapter. Neither is a cloud-router/stage-A/HTTP CLI path. +- `tests/no-cloud-boundary.test.ts` enforces removal of the retired shared cloud runtime from dependencies, tracked source, and built output. + +Therefore the amended task's local-versus-API parity work is **N/A**. Implementing a new remote CLI protocol would invent an architecture that this repository intentionally does not expose. + +## Command inventory + +The inventory comes from the default in `parseArgs` and every `parsed.command === ...` branch in `src/cli/index.ts`, not from README documentation. Option variants such as `smoke --all` remain part of their registered command row. + +| command | works locally? | works against the API backend (or N/A)? | dead? | changed in this PR? | +| --- | --- | --- | --- | --- | +| `gateway` | Yes; defaults to help | N/A | No | No | +| `gateway help` | Yes | N/A | No | No | +| `gateway --help` | Yes | N/A | No | No | +| `gateway --version` | Yes | N/A | No | No | +| `gateway budget-add` | Yes; reads, validates, and updates the selected local config | N/A | No | No | +| `gateway budget-list` | Yes; loads and lists normalized config budgets | N/A | No | No | +| `gateway budget-remaining` | Yes; reads configured JSONL and/or direct SQLite/PostgreSQL ledger storage | N/A | No | No | +| `gateway budget-reset` | Yes; validates and updates the selected local config | N/A | No | No | +| `gateway route` | Yes; performs an in-process dry-run route decision | N/A | No | No | +| `gateway routes` | Yes; lists in-process normalized route configuration | N/A | No | No | +| `gateway uninstall` | Yes; removes the selected local config and configured JSONL ledger with safeguards | N/A | No | No | +| `gateway remove` | Yes; guarded `uninstall` alias requiring `--all --yes` | N/A | No | No | +| `gateway validate` | Yes; validates the selected local config | N/A | No | No | +| `gateway smoke` | Yes; performs provider traffic when credentials exist and reports skip/failure semantics otherwise; `--all` is implemented | N/A | No | No | +| `gateway serve` | Yes; starts the configured HTTP gateway server | N/A | No | No | + +## Changes + +- No production command implementation changed because the audit found no dead command and no applicable API backend. +- `tests/cli-help.test.ts` now fails if parser registration drifts from the audited command inventory and verifies the bare, `help`, and `--help` forms. + +## Verification + +- `git diff --check` +- Parser inventory extraction independently verified all 14 explicit command tokens; the table additionally includes the parser's implicit bare invocation. +- Run `bun run check` in a Bun-enabled environment for typecheck, build, and the complete test suite. diff --git a/tests/cli-help.test.ts b/tests/cli-help.test.ts index 0f531f6..d861162 100644 --- a/tests/cli-help.test.ts +++ b/tests/cli-help.test.ts @@ -1,3 +1,4 @@ +import { readFileSync } from "node:fs"; import { describe, expect, test } from "bun:test"; import { gatewayVersion } from "../src/version"; @@ -15,12 +16,38 @@ function text(bytes: Uint8Array): string { } describe("gateway CLI help", () => { + test("keeps the registered command inventory explicit", () => { + const source = readFileSync(new URL("../src/cli/index.ts", import.meta.url), "utf8"); + const registered = [...source.matchAll(/parsed\.command === "([^"]+)"/g)] + .map((match) => match[1]) + .filter((command): command is string => Boolean(command)); + + expect([...new Set(registered)].sort()).toEqual([ + "--help", + "--version", + "budget-add", + "budget-list", + "budget-remaining", + "budget-reset", + "help", + "remove", + "route", + "routes", + "serve", + "smoke", + "uninstall", + "validate", + ]); + }); + test("supports standard top-level help and version flags", () => { - const help = runGateway(["--help"]); - expect(help.exitCode).toBe(0); - expect(text(help.stdout)).toContain("gateway budget-add"); - expect(text(help.stdout)).toContain("--max-input-tokens"); - expect(text(help.stderr)).toBe(""); + for (const args of [[], ["help"], ["--help"]]) { + const help = runGateway(args); + expect(help.exitCode).toBe(0); + expect(text(help.stdout)).toContain("gateway budget-add"); + expect(text(help.stdout)).toContain("--max-input-tokens"); + expect(text(help.stderr)).toBe(""); + } const version = runGateway(["--version"]); expect(version.exitCode).toBe(0);