mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 22:02:08 +03:00
ci(docs): harden fabricated-docs checker + enforce --strict (QG v2 Fase 9 T9) (#4149)
Harden fabricated-docs checker (precision: env helpers/code-identifiers/api-prefixes/env-contract/tutorial-placeholders) + enforce --strict (QG v2 Fase 9 T9). Inclui testes anti-over-suppression + correção de refs stale de docs. Integrado em release/v3.8.29.
This commit is contained in:
committed by
GitHub
parent
7fd4723f15
commit
0ca7a01c4b
@@ -80,7 +80,7 @@ claude.ai API
|
||||
| File | Purpose |
|
||||
| ----------------------------------------------------- | -------------------------------------------- |
|
||||
| `src/shared/constants/providers.ts` | Provider registration (WEB_COOKIE_PROVIDERS) |
|
||||
| `src/lib/providers/wrappers/claudeWeb.ts` | Type definitions + cookie utilities |
|
||||
| `src/lib/providers/webCookieAuth.ts` | Cookie utilities (normalize/extract session cookies) |
|
||||
| `open-sse/executors/claude-web.ts` | Executor implementation |
|
||||
| `open-sse/executors/index.ts` | Executor registration |
|
||||
| `open-sse/services/claudeTlsClient.ts` | TLS fingerprint spoofing via tls-client-node |
|
||||
|
||||
@@ -292,7 +292,7 @@ Services (business logic):
|
||||
- Thinking budget management: `open-sse/services/thinkingBudget.ts`
|
||||
- Wildcard model routing: `open-sse/services/wildcardRouter.ts`
|
||||
- Rate limit management: `open-sse/services/rateLimitManager.ts`
|
||||
- Circuit breaker: `open-sse/services/circuitBreaker.ts`
|
||||
- Circuit breaker: `src/shared/utils/circuitBreaker.ts`
|
||||
- Context handoff: `open-sse/services/contextHandoff.ts` — handoff summary generation and injection for context-relay strategy
|
||||
- Compression: `open-sse/services/compression/*` — proactive compression before provider translation;
|
||||
includes Caveman rules, RTK filters, stacked pipelines, compression combos, stats, and validation
|
||||
@@ -308,17 +308,17 @@ Services (business logic):
|
||||
|
||||
Domain layer modules:
|
||||
|
||||
- Cost rules/budgets: `src/lib/domain/costRules.ts`
|
||||
- Fallback policy: `src/lib/domain/fallbackPolicy.ts`
|
||||
- Combo resolver: `src/lib/domain/comboResolver.ts`
|
||||
- Lockout policy: `src/lib/domain/lockoutPolicy.ts`
|
||||
- Cost rules/budgets: `src/domain/costRules.ts`
|
||||
- Fallback policy: `src/domain/fallbackPolicy.ts`
|
||||
- Combo resolver: `src/domain/comboResolver.ts`
|
||||
- Lockout policy: `src/domain/lockoutPolicy.ts`
|
||||
- Policy engine: `src/domain/policyEngine.ts` — centralized lockout → budget → fallback evaluation
|
||||
- Error codes catalog: `src/lib/domain/errorCodes.ts`
|
||||
- Request ID: `src/lib/domain/requestId.ts`
|
||||
- Fetch timeout: `src/lib/domain/fetchTimeout.ts`
|
||||
- Request telemetry: `src/lib/domain/requestTelemetry.ts`
|
||||
- Compliance/audit: `src/lib/domain/compliance/index.ts`
|
||||
- Eval runner: `src/lib/domain/evalRunner.ts`
|
||||
- Error codes catalog: `src/shared/constants/errorCodes.ts`
|
||||
- Request ID: `src/shared/utils/requestId.ts`
|
||||
- Fetch timeout: `src/shared/utils/fetchTimeout.ts`
|
||||
- Request telemetry: `src/shared/utils/requestTelemetry.ts`
|
||||
- Compliance/audit: `src/lib/compliance/index.ts`
|
||||
- Eval runner: `src/lib/evals/evalRunner.ts`
|
||||
- Domain state persistence: `src/lib/db/domainState.ts` — SQLite CRUD for fallback chains, budgets, cost history, lockout state, circuit breakers
|
||||
|
||||
OAuth provider modules (16 individual files under `src/lib/oauth/providers/`):
|
||||
|
||||
@@ -143,4 +143,4 @@ Namespaces added by Group B:
|
||||
| `activity.*` | All Activity page strings (title, verbs, filters, empty state) |
|
||||
|
||||
Source-of-truth locales: `pt-BR` and `en`. All other 39 locales fall back to
|
||||
English via the fallback mechanism (`src/i18n/fallback.ts` or `next-intl` fallback).
|
||||
English via the `next-intl` fallback mechanism (configured in `src/i18n/config.ts`).
|
||||
|
||||
@@ -360,7 +360,6 @@ open-sse/
|
||||
| `check-supported-node-runtime.ts` | Validate current Node version is supported |
|
||||
| `check-pr-test-policy.mjs` | Enforce "tests required" rule on production code changes |
|
||||
| **`gen-provider-reference.ts`** | NEW: auto-generate `docs/reference/PROVIDER_REFERENCE.md` from catalog |
|
||||
| `generate-docs-index.mjs` | Build `src/app/docs/lib/docs-auto-generated.ts` from `docs/*.md` |
|
||||
| `i18n/generate-multilang.mjs` | Translate UI strings + docs via Google Translate |
|
||||
| `i18n_autotranslate.py` | LLM-based doc translation pipeline |
|
||||
| `validate_translation.py` | Per-locale translation validation |
|
||||
|
||||
@@ -129,7 +129,7 @@ from the current `PlaygroundState`. API key placeholder is always `$OMNIROUTE_AP
|
||||
|
||||
`PresetPicker.tsx` → `usePresets.ts` → `/api/playground/presets/*`:
|
||||
|
||||
- Stored in `playground_presets` SQLite table (migration `076_playground_presets.sql`).
|
||||
- Stored in `playground_presets` SQLite table (migration `084_playground_presets.sql`).
|
||||
- Each preset stores: `name`, `endpoint`, `model`, `system`, `params_json`, `created_at`.
|
||||
- CRUD: `GET` list, `POST` create, `GET /:id`, `PUT /:id`, `DELETE /:id`.
|
||||
|
||||
@@ -186,7 +186,7 @@ Auth: optional (`REQUIRE_API_KEY`). Errors via `buildErrorBody()` (Hard Rule #12
|
||||
| `src/app/api/playground/improve-prompt/route.ts` | Improve-prompt REST route |
|
||||
| `src/app/api/playground/presets/route.ts` | Presets list + create |
|
||||
| `src/app/api/playground/presets/[id]/route.ts` | Presets get/update/delete |
|
||||
| `src/lib/db/migrations/076_playground_presets.sql` | DB migration |
|
||||
| `src/lib/db/migrations/084_playground_presets.sql` | DB migration |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -571,15 +571,14 @@ omniroute setup --add-provider \
|
||||
|
||||
Recognized environment variables for non-interactive setup:
|
||||
|
||||
| Var | Purpose |
|
||||
| ----------------------------- | -------------------------------------------- |
|
||||
| `OMNIROUTE_SETUP_PASSWORD` | Admin password (>=8 chars) |
|
||||
| `OMNIROUTE_PROVIDER` | Provider id (e.g. `openai`, `anthropic`) |
|
||||
| `OMNIROUTE_PROVIDER_NAME` | Display name for the connection |
|
||||
| `OMNIROUTE_PROVIDER_BASE_URL` | Optional OpenAI-compatible base URL override |
|
||||
| `OMNIROUTE_API_KEY` | Provider API key |
|
||||
| `OMNIROUTE_DEFAULT_MODEL` | Optional default model |
|
||||
| `DATA_DIR` | Override the OmniRoute data directory |
|
||||
| Var | Purpose |
|
||||
| ------------------ | ----------------------------------------------------------------- |
|
||||
| `OMNIROUTE_API_KEY` | Provider API key (bound to `--api-key` via Commander `.env()`) |
|
||||
| `DATA_DIR` | Override the OmniRoute data directory |
|
||||
|
||||
All other non-interactive inputs are passed as flags, not environment variables:
|
||||
`--password`, `--provider`, `--provider-name`, `--provider-base-url`, `--default-model`
|
||||
(see the `omniroute setup` options above).
|
||||
|
||||
### Diagnostics
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ lastUpdated: 2026-05-13
|
||||
> Every variable documented here must also appear in `.env.example`, and
|
||||
> every variable in `.env.example` must appear here. `npm run check:env-doc-sync`
|
||||
> enforces this on commit and in CI. To omit a variable on purpose, add it to
|
||||
> the allowlist inside `scripts/check-env-doc-sync.mjs`.
|
||||
> the allowlist inside `scripts/check/check-env-doc-sync.mjs`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ When adding a new route or executor, copy the assertion pattern from this file.
|
||||
## Related controls
|
||||
|
||||
- `js/stack-trace-exposure` CodeQL alerts in `.github/security` should always be **either** fixed via these helpers **or** dismissed with a comment citing this doc.
|
||||
- The `pino` redaction config (`src/lib/log/redaction.ts` — if present) handles structured log redaction separately. This doc covers only the response-message surface.
|
||||
- The `pino` redaction config (`src/shared/utils/logRedaction.ts`) handles structured log redaction separately. This doc covers only the response-message surface.
|
||||
- Upstream-header denylist (`src/shared/constants/upstreamHeaders.ts`) covers header leakage — keep both files aligned when adding a new exfiltration concern.
|
||||
|
||||
## Upstream details passthrough
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
"check:docs-counts": "node scripts/check/check-docs-counts-sync.mjs",
|
||||
"check:deprecated-versions": "node scripts/check/check-deprecated-versions.mjs",
|
||||
"check:doc-links": "node scripts/check/check-doc-links.mjs",
|
||||
"check:fabricated-docs": "node scripts/check/check-fabricated-docs.mjs",
|
||||
"check:fabricated-docs": "node scripts/check/check-fabricated-docs.mjs --strict",
|
||||
"check:docs-all": "npm run check:docs-sync && npm run check:docs-counts && npm run check:env-doc-sync && npm run check:deprecated-versions && npm run check:doc-links && npm run check:fabricated-docs",
|
||||
"docs:render-diagrams": "node scripts/docs/render-diagrams.mjs",
|
||||
"i18n:run": "node scripts/i18n/run-translation.mjs",
|
||||
|
||||
@@ -60,6 +60,17 @@ const KNOWN_HOOKS = new Set([
|
||||
"onActivate",
|
||||
"onDeactivate",
|
||||
"onUninstall",
|
||||
// Real callbacks wired in code that docs reference (verified present in src/):
|
||||
// onChunk/onFirstChunk — streaming callbacks (src/shared/utils/streamTracker.ts,
|
||||
// playground ChatTab.tsx); onServerStatus/onPortChanged/onUpdateStatus — Electron
|
||||
// IPC callbacks (src/shared/hooks/useElectron.ts, HomePageClient.tsx);
|
||||
// onEmpty — model-metadata registry callback (src/lib/modelMetadataRegistry.ts).
|
||||
"onChunk",
|
||||
"onFirstChunk",
|
||||
"onServerStatus",
|
||||
"onPortChanged",
|
||||
"onUpdateStatus",
|
||||
"onEmpty",
|
||||
]);
|
||||
|
||||
// Common false-positives the heuristic would otherwise flag. Add to this
|
||||
@@ -85,6 +96,21 @@ const ENV_VAR_ALLOWLIST = new Set([
|
||||
"OMNIROUTE_URL", // used by ad-hoc tooling, validated elsewhere
|
||||
"OMNIROUTE_KEY", // ditto
|
||||
"OPENCODE_API_KEY", // ditto
|
||||
// ── External-tool / spawn-injected / ops env vars ────────────────────────
|
||||
// Real environment variables, but they belong to an UPSTREAM CLI/tool, a
|
||||
// docker-compose/electron-build pipeline, or are injected into a spawned
|
||||
// subprocess — never read via `process.env.X` in OmniRoute's own source, so the
|
||||
// code-read index can't see them. Documented (correctly) in the relevant guides.
|
||||
"COPILOT_PROVIDER_BASE_URL", // GitHub Copilot CLI ≥v1.0.19's own env var (AGENTBRIDGE.md)
|
||||
"OPENAI_BASE_URL", // env var OmniRoute passes to downstream CLIs (AGENT_PROTOCOLS_GUIDE.md)
|
||||
"NINEROUTER_API_KEY", // injected into the 9router subprocess at spawn (EMBEDDED-SERVICES.md)
|
||||
"CLAUDE_CODE_MAX_OUTPUT_TOKENS", // Claude Code CLI's own env var (CODEX-CLI-CONFIGURATION.md)
|
||||
"CODEX_HOME", // Codex CLI's own config-home env var (CODEX-CLI-CONFIGURATION.md)
|
||||
"REDIS_PORT", // docker-compose host-port override (DOCKER_GUIDE.md)
|
||||
"AUTO_UPDATE_HOST_REPO_DIR", // docker-compose self-update mount (DOCKER_GUIDE.md)
|
||||
"LINUX_GPG_KEY", // electron AppImage signing key, CI/build only (ELECTRON_GUIDE.md)
|
||||
"BRANCH_LOCK_TOKEN", // release branch-protection ops token (QUALITY_GATE_PLAYBOOK.md)
|
||||
"NEXT_LOCALE", // next-intl locale cookie name (I18N.md)
|
||||
]);
|
||||
|
||||
// Common pluralized / column-header all-caps that aren't env vars
|
||||
@@ -275,6 +301,18 @@ const ENV_VAR_DENYLIST = new Set([
|
||||
"KNOWN_STALE_DOC_REFS", // export const in check-docs-symbols.mjs
|
||||
"KNOWN_MISSING", // export const in check-fetch-targets.mjs
|
||||
"KNOWN_RAW_SQL", // export const in check-db-rules.mjs
|
||||
// ── Error / Node codes documented in prose (string-literal codes, not env vars) ──
|
||||
"URL_GUARD_BLOCKED", // HTTP 422 guard-violation code (ARCHITECTURE.md)
|
||||
"AUTHZ_NOT_INITIALIZED", // AuthzAssertionError code (AUTHZ_GUIDE.md)
|
||||
"MODULE_NOT_FOUND", // Node runtime error code watched by service supervisor (ELECTRON_GUIDE.md)
|
||||
"ERR_DLOPEN_FAILED", // Node native-module load error code (ELECTRON_GUIDE.md)
|
||||
// ── Code-symbol / naming-convention examples documented in prose ─────────────
|
||||
"UPPER_SNAKE", // the literal naming-convention token in the style guide (CODEBASE_DOCUMENTATION.md)
|
||||
"DEFAULT_TIMEOUT", // example constant name in the UPPER_SNAKE convention row (AGENTS.md)
|
||||
"SIDEBAR_DEFINITIONS", // code constant referenced in prose (MONITORING_SECTIONS.md)
|
||||
"LOCAL_ONLY", // routeGuard classification label (AGENTBRIDGE.md)
|
||||
"SPAWN_CAPABLE", // routeGuard classification label (AGENTBRIDGE.md)
|
||||
"ZEROGRAVITY_SENSITIVE_WORDS", // cross-project constant named in a comparison (STEALTH_GUIDE.md)
|
||||
]);
|
||||
|
||||
/** Endpoints that don't follow the standard route.ts pattern. */
|
||||
@@ -306,6 +344,10 @@ const ENDPOINT_ALLOWLIST = new Set([
|
||||
"/api/mcp/stream", // Streamable HTTP MCP transport
|
||||
"/api/mcp/sse", // SSE MCP transport
|
||||
"/api/health",
|
||||
// Upstream/external provider endpoints documented in provider guides — these are
|
||||
// paths on the UPSTREAM service (Claude.ai web, Blackbox), not OmniRoute routes.
|
||||
"/api/organizations/{orgId}/chat_conversations/{convId}/completion", // claude-web upstream
|
||||
"/api/chat", // Blackbox Web upstream (validated-token target)
|
||||
]);
|
||||
|
||||
/** Doc files to skip (auto-generated, vendored, or third-party). */
|
||||
@@ -316,12 +358,27 @@ const SKIP_DOC_FILES = new Set([
|
||||
// Point-in-time documentation audit (v3.8.24): intentionally references drift,
|
||||
// counts, and not-yet-existing files as part of documenting them — not living docs.
|
||||
"docs/ops/DOCUMENTATION_AUDIT_REPORT.md",
|
||||
// Design / research / plan docs: by definition describe not-yet-built files and
|
||||
// proposed (not-yet-shipped) endpoints (each carries a `Status: Design`/`Active
|
||||
// research`/`Plano` header). Same rationale as the audit report above — these are
|
||||
// forward-looking specs, not living API docs, so their forward references are
|
||||
// expected, not fabrications.
|
||||
"docs/research", // DISCOVERY_TOOL_DESIGN.md, UNLIMITED_LLM_ACCESS.md, …
|
||||
"docs/superpowers/plans", // dated implementation plans (files described before they exist)
|
||||
// Release notes are historical, point-in-time records: they intentionally describe
|
||||
// modules/paths as they were at that release (e.g. a module later moved or renamed).
|
||||
// Rewriting them to today's layout would falsify history — out of scope for a
|
||||
// living-docs accuracy gate.
|
||||
"docs/releases",
|
||||
// Forward-looking coverage plan: a `- [ ]` checklist of test targets and helper
|
||||
// components to be created. Same rationale as the design/plan docs above.
|
||||
"docs/ops/COVERAGE_PLAN.md",
|
||||
]);
|
||||
|
||||
// ── File discovery ─────────────────────────────────────────────────────────
|
||||
|
||||
function walkMarkdown(dir, out = []) {
|
||||
const abs = path.join(ROOT, dir);
|
||||
function walkMarkdown(dir, out = [], root = ROOT) {
|
||||
const abs = path.join(root, dir);
|
||||
if (!fs.existsSync(abs)) return out;
|
||||
const stat = fs.statSync(abs);
|
||||
if (stat.isFile()) {
|
||||
@@ -332,17 +389,17 @@ function walkMarkdown(dir, out = []) {
|
||||
if (name === "node_modules" || name.startsWith(".")) continue;
|
||||
const childAbs = path.join(abs, name);
|
||||
const s = fs.statSync(childAbs);
|
||||
if (s.isDirectory()) walkMarkdown(path.relative(ROOT, childAbs), out);
|
||||
if (s.isDirectory()) walkMarkdown(path.relative(root, childAbs), out, root);
|
||||
else if (childAbs.endsWith(".md") || childAbs.endsWith(".mdx")) out.push(childAbs);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function allScanFiles() {
|
||||
function allScanFiles(root = ROOT) {
|
||||
const files = [];
|
||||
for (const p of SCAN_PATHS) walkMarkdown(p, files);
|
||||
for (const p of SCAN_PATHS) walkMarkdown(p, files, root);
|
||||
return files.filter((f) => {
|
||||
const rel = path.relative(ROOT, f);
|
||||
const rel = path.relative(root, f);
|
||||
for (const skip of SKIP_DOC_FILES) {
|
||||
if (rel === skip || rel.startsWith(skip + path.sep)) return false;
|
||||
}
|
||||
@@ -352,22 +409,39 @@ function allScanFiles() {
|
||||
|
||||
// ── Codebase index ─────────────────────────────────────────────────────────
|
||||
|
||||
function buildCodebaseIndex() {
|
||||
// Env var helper wrappers used across OmniRoute — envInt(NAME, 5), envBool(NAME),
|
||||
// envStr(NAME), … — read the named var from the environment, so a string-literal
|
||||
// argument is a genuine env-var read, equivalent to a direct process.env member read.
|
||||
// (Comment avoids a literal `process.env.<NAME>` token so the sibling env-doc-sync
|
||||
// grep does not mistake this example for a real env-var read.)
|
||||
const ENV_HELPER_CALL =
|
||||
/\benv(?:Int|Bool|Str|Num|Float|String|Flag|Raw|List|Json)?\(\s*["'`]([A-Z][A-Z0-9_]+)["'`]/g;
|
||||
|
||||
export function buildCodebaseIndex(root = ROOT) {
|
||||
// Set of /api/... paths that have a route.ts handler.
|
||||
const apiRoutes = new Set();
|
||||
// Set of /api/... prefixes that are an ancestor of (or equal to) a real route.ts.
|
||||
// A documented prefix like /api/cloud/ is valid even without a route.ts at that
|
||||
// exact level, as long as some src/app/api/cloud/**/route.ts exists.
|
||||
const apiPrefixes = new Set();
|
||||
// Map of /api/... → methods implemented in route.ts
|
||||
const apiMethods = new Map();
|
||||
|
||||
function dynToBrace(seg) {
|
||||
// [id] → {id}, [...path] → {path} — match the doc convention for dynamic segments.
|
||||
return seg.replace(/^\[\.\.\.(.+)\]$/, "{$1}").replace(/^\[(.+)\]$/, "{$1}");
|
||||
}
|
||||
|
||||
function walkApiRoutes(dir) {
|
||||
const abs = path.join(ROOT, dir);
|
||||
const abs = path.join(root, dir);
|
||||
if (!fs.existsSync(abs)) return;
|
||||
for (const name of fs.readdirSync(abs)) {
|
||||
const child = path.join(abs, name);
|
||||
const s = fs.statSync(child);
|
||||
if (s.isDirectory()) walkApiRoutes(path.relative(ROOT, child));
|
||||
if (s.isDirectory()) walkApiRoutes(path.relative(root, child));
|
||||
else if (name === "route.ts" || name === "route.mjs") {
|
||||
// Build the route path from the directory hierarchy
|
||||
const rel = path.relative(ROOT, child).replace(/\\/g, "/");
|
||||
const rel = path.relative(root, child).replace(/\\/g, "/");
|
||||
const parts = rel.split("/");
|
||||
// drop "src/app/api" and "route.ts"
|
||||
parts.shift(); // src
|
||||
@@ -378,6 +452,15 @@ function buildCodebaseIndex() {
|
||||
apiRoutes.add(routePath);
|
||||
apiRoutes.add(routePath + "/"); // trailing slash variant
|
||||
|
||||
// Register every ancestor prefix (and its {brace} dynamic-segment variant)
|
||||
// so documented prefixes-with-subroutes resolve.
|
||||
for (let i = 1; i <= parts.length; i++) {
|
||||
const prefix = "/api/" + parts.slice(0, i).join("/");
|
||||
const bracePrefix = "/api/" + parts.slice(0, i).map(dynToBrace).join("/");
|
||||
apiPrefixes.add(prefix);
|
||||
apiPrefixes.add(bracePrefix);
|
||||
}
|
||||
|
||||
// Read the file to find exported HTTP methods
|
||||
try {
|
||||
const content = fs.readFileSync(child, "utf8");
|
||||
@@ -397,29 +480,47 @@ function buildCodebaseIndex() {
|
||||
}
|
||||
walkApiRoutes("src/app/api");
|
||||
|
||||
// Set of env var names that are actually read in code.
|
||||
// Set of env var names that are actually read in code, and the set of
|
||||
// ALL_CAPS code identifiers (export const / enum / object-literal keys). A
|
||||
// documented `UPPER_SNAKE` that resolves to a code identifier (e.g.
|
||||
// LOCAL_ONLY_API_PREFIXES, HALF_OPEN) is NOT a fabricated env var.
|
||||
const envVars = new Set();
|
||||
const codeIdentifiers = new Set();
|
||||
function walkForEnv(dir) {
|
||||
const abs = path.join(ROOT, dir);
|
||||
const abs = path.join(root, dir);
|
||||
if (!fs.existsSync(abs)) return;
|
||||
const skipDirs = new Set(["node_modules", ".next", "dist", ".build", "coverage"]);
|
||||
for (const name of fs.readdirSync(abs)) {
|
||||
if (skipDirs.has(name)) continue;
|
||||
const child = path.join(abs, name);
|
||||
const s = fs.statSync(child);
|
||||
if (s.isDirectory()) walkForEnv(path.relative(ROOT, child));
|
||||
if (s.isDirectory()) walkForEnv(path.relative(root, child));
|
||||
else if (/\.(ts|tsx|js|mjs|cjs)$/.test(name)) {
|
||||
try {
|
||||
const content = fs.readFileSync(child, "utf8");
|
||||
// process.env.X
|
||||
const m1 = content.matchAll(/process\.env\.([A-Z][A-Z0-9_]+)/g);
|
||||
for (const m of m1) envVars.add(m[1]);
|
||||
for (const m of content.matchAll(/process\.env\.([A-Z][A-Z0-9_]+)/g)) envVars.add(m[1]);
|
||||
// process.env["X"] / process.env['X'] (bracket notation)
|
||||
for (const m of content.matchAll(/process\.env\[\s*["'`]([A-Z][A-Z0-9_]+)["'`]\s*\]/g))
|
||||
envVars.add(m[1]);
|
||||
// env.X (destructured in some handlers)
|
||||
const m2 = content.matchAll(/\benv\.([A-Z][A-Z0-9_]+)\b/g);
|
||||
for (const m of m2) envVars.add(m[1]);
|
||||
for (const m of content.matchAll(/\benv\.([A-Z][A-Z0-9_]+)\b/g)) envVars.add(m[1]);
|
||||
// env["X"] (bracket on a destructured env binding)
|
||||
for (const m of content.matchAll(/\benv\[\s*["'`]([A-Z][A-Z0-9_]+)["'`]\s*\]/g))
|
||||
envVars.add(m[1]);
|
||||
// envInt("X", …) / envBool("X") / envStr("X") … helper wrappers
|
||||
for (const m of content.matchAll(ENV_HELPER_CALL)) envVars.add(m[1]);
|
||||
// import.meta.env.X (Vite-style, unlikely here but cheap)
|
||||
const m3 = content.matchAll(/import\.meta\.env\.([A-Z][A-Z0-9_]+)/g);
|
||||
for (const m of m3) envVars.add(m[1]);
|
||||
for (const m of content.matchAll(/import\.meta\.env\.([A-Z][A-Z0-9_]+)/g))
|
||||
envVars.add(m[1]);
|
||||
// export const / const / let / var / enum NAME — JS identifiers, not env vars.
|
||||
for (const m of content.matchAll(
|
||||
/\b(?:export\s+)?(?:const|let|var|enum)\s+([A-Z][A-Z0-9_]{2,})\b/g
|
||||
))
|
||||
codeIdentifiers.add(m[1]);
|
||||
// Object-literal / enum members on their own line: `HALF_OPEN: "HALF_OPEN"`.
|
||||
for (const m of content.matchAll(/^[ \t]*([A-Z][A-Z0-9_]{2,})[ \t]*[:=]/gm))
|
||||
codeIdentifiers.add(m[1]);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
@@ -430,16 +531,42 @@ function buildCodebaseIndex() {
|
||||
walkForEnv("open-sse");
|
||||
walkForEnv("bin");
|
||||
walkForEnv("scripts");
|
||||
// Env vars that are only read by the test harness (e.g. RUN_CHAOS_INT) are still
|
||||
// real env vars and must not be flagged as fabricated.
|
||||
walkForEnv("tests");
|
||||
|
||||
// Env contract maintained by the sibling gate (check-env-doc-sync.mjs): a var
|
||||
// listed in .env.example or docs/reference/ENVIRONMENT.md is, by definition, a
|
||||
// documented OmniRoute env var (including external-CLI / docker / electron vars
|
||||
// that are not read via process.env in our own source).
|
||||
function readEnvContract() {
|
||||
try {
|
||||
const t = fs.readFileSync(path.join(root, ".env.example"), "utf8");
|
||||
for (const line of t.split("\n")) {
|
||||
const m = line.match(/^#?\s*([A-Z][A-Z0-9_]+)\s*=/);
|
||||
if (m) envVars.add(m[1]);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
const t = fs.readFileSync(path.join(root, "docs", "reference", "ENVIRONMENT.md"), "utf8");
|
||||
for (const m of t.matchAll(/`([A-Z][A-Z0-9_]{2,})`/g)) envVars.add(m[1]);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
readEnvContract();
|
||||
|
||||
// Set of `omniroute <subcommand>` strings that exist in bin/
|
||||
const cliCommands = new Set();
|
||||
function walkCli(dir) {
|
||||
const abs = path.join(ROOT, dir);
|
||||
const abs = path.join(root, dir);
|
||||
if (!fs.existsSync(abs)) return;
|
||||
for (const name of fs.readdirSync(abs)) {
|
||||
const child = path.join(abs, name);
|
||||
const s = fs.statSync(child);
|
||||
if (s.isDirectory()) walkCli(path.relative(ROOT, child));
|
||||
if (s.isDirectory()) walkCli(path.relative(root, child));
|
||||
else if (/\.(mjs|js|ts)$/.test(name)) {
|
||||
try {
|
||||
const content = fs.readFileSync(child, "utf8");
|
||||
@@ -460,7 +587,7 @@ function buildCodebaseIndex() {
|
||||
}
|
||||
walkCli("bin");
|
||||
|
||||
return { apiRoutes, apiMethods, envVars, cliCommands };
|
||||
return { apiRoutes, apiPrefixes, apiMethods, envVars, codeIdentifiers, cliCommands };
|
||||
}
|
||||
|
||||
// ── Doc scanning ───────────────────────────────────────────────────────────
|
||||
@@ -490,18 +617,44 @@ function lineOf(text, idx) {
|
||||
return line;
|
||||
}
|
||||
|
||||
function scanDocFile(absPath, index) {
|
||||
const rel = path.relative(ROOT, absPath);
|
||||
export function scanDocFile(absPath, index, root = ROOT) {
|
||||
const rel = path.relative(root, absPath);
|
||||
const text = fs.readFileSync(absPath, "utf8");
|
||||
const textNoCode = stripCodeBlocksAndFences(text);
|
||||
const findings = [];
|
||||
|
||||
// 1) API endpoints
|
||||
for (const m of textNoCode.matchAll(COARSE_PATTERNS.apiPath)) {
|
||||
const p = m[0].replace(/[\[\]\{\}]/g, ""); // strip wildcards for lookup
|
||||
const candidate = p.replace(/\/$/, "");
|
||||
if (ENDPOINT_ALLOWLIST.has(candidate) || ENDPOINT_ALLOWLIST.has(candidate + "/")) continue;
|
||||
if (index.apiRoutes.has(candidate) || index.apiRoutes.has(candidate + "/")) continue;
|
||||
// Normalize wildcard segments to {brace} form so [id] / [...path] / {id} all
|
||||
// compare equally against the indexed routes/prefixes.
|
||||
const normalized = m[0].replace(/\[\.\.\.(.+?)\]/g, "{$1}").replace(/\[(.+?)\]/g, "{$1}");
|
||||
const candidate = normalized.replace(/\/$/, "");
|
||||
const stripped = m[0].replace(/[\[\]\{\}]/g, "").replace(/\/$/, ""); // legacy lookup form
|
||||
if (
|
||||
ENDPOINT_ALLOWLIST.has(candidate) ||
|
||||
ENDPOINT_ALLOWLIST.has(candidate + "/") ||
|
||||
ENDPOINT_ALLOWLIST.has(stripped) ||
|
||||
ENDPOINT_ALLOWLIST.has(stripped + "/")
|
||||
)
|
||||
continue;
|
||||
if (
|
||||
index.apiRoutes.has(stripped) ||
|
||||
index.apiRoutes.has(stripped + "/") ||
|
||||
index.apiRoutes.has(candidate) ||
|
||||
index.apiRoutes.has(candidate + "/")
|
||||
)
|
||||
continue;
|
||||
// Prefix-with-subroutes: a documented prefix like /api/cloud/ or /api/services/{name}/
|
||||
// is valid when some src/app/api/<prefix>/**/route.ts exists. Accept when the
|
||||
// documented path is (or is an ancestor of) a real route prefix, or vice-versa.
|
||||
let isPrefix = false;
|
||||
for (const pre of index.apiPrefixes) {
|
||||
if (pre === candidate || pre.startsWith(candidate + "/") || candidate.startsWith(pre + "/")) {
|
||||
isPrefix = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isPrefix) continue;
|
||||
// Allow docs that describe intended-but-not-yet-shipped routes by skipping lines that say "planned" / "TBD" / "future"
|
||||
const ln = lineOf(text, m.index);
|
||||
const lineText = text.split("\n")[ln - 1] || "";
|
||||
@@ -525,11 +678,30 @@ function scanDocFile(absPath, index) {
|
||||
if (ENV_VAR_ALLOWLIST.has(name)) continue;
|
||||
if (!/_/.test(name)) continue; // real env vars have an underscore
|
||||
if (index.envVars.has(name)) continue;
|
||||
// Documented ALL_CAPS that resolves to a JS identifier (export const / enum /
|
||||
// object-literal key) is a code symbol, not a fabricated env var.
|
||||
// E.g. LOCAL_ONLY_API_PREFIXES, HALF_OPEN, A2A_SKILL_HANDLERS, MCP_SCOPE_PRESETS.
|
||||
if (index.codeIdentifiers.has(name)) continue;
|
||||
if (/^X-[A-Z]/.test(name)) continue;
|
||||
if (ENV_VAR_DENYLIST.has(name)) continue;
|
||||
const ln = lineOf(text, m.index);
|
||||
const lineText = text.split("\n")[ln - 1] || "";
|
||||
// Use the line as seen in the stripped text (where m.index is valid) for context
|
||||
// checks — fenced-code removal shifts offsets, so text.split()[ln-1] can be wrong.
|
||||
const lineStart = textNoCode.lastIndexOf("\n", m.index) + 1;
|
||||
let lineEnd = textNoCode.indexOf("\n", m.index);
|
||||
if (lineEnd === -1) lineEnd = textNoCode.length;
|
||||
const lineText = textNoCode.slice(lineStart, lineEnd);
|
||||
if (/example|placeholder|todo|tbd|\.\.\./i.test(lineText)) continue;
|
||||
// A doc that explicitly states a var does NOT exist / is not implemented is
|
||||
// documenting its absence, not fabricating it. Examples:
|
||||
// "`MEMORY_RRF_VECTOR_WEIGHT` … do not exist"
|
||||
// "a `ZED_CONFIG_PATH` environment variable override is not yet implemented"
|
||||
if (
|
||||
/\b(?:do(?:es)?\s+not\s+exist|no\s+such|not\s+a\s+real|isn't\s+a\s+real|never\s+(?:read|exists?)|not\s+(?:yet\s+)?(?:implemented|supported))\b/i.test(
|
||||
lineText
|
||||
)
|
||||
)
|
||||
continue;
|
||||
findings.push({
|
||||
kind: "env-var",
|
||||
value: name,
|
||||
@@ -582,10 +754,20 @@ function scanDocFile(absPath, index) {
|
||||
// 5) File references
|
||||
for (const m of textNoCode.matchAll(COARSE_PATTERNS.fileRef)) {
|
||||
const ref = m[1].replace(/\\/g, "/");
|
||||
const abs = path.join(ROOT, ref);
|
||||
const abs = path.join(root, ref);
|
||||
if (fs.existsSync(abs)) continue;
|
||||
// Allow README/AGENTS to mention example files explicitly in a non-verified way
|
||||
if (/\{\{|\.\.\./.test(ref)) continue; // templated / placeholder
|
||||
// Tutorial placeholders in the "how to add a …" scenarios are intentional
|
||||
// stand-ins, not real files: src/app/api/your-route/route.ts,
|
||||
// src/lib/db/yourModule.ts, src/lib/guardrails/myGuardrail.ts, etc.
|
||||
if (/(?:^|\/)(?:your-|your[A-Z]|my[A-Z])/.test(ref)) continue;
|
||||
// Skip matches that are only the tail of a longer path, not a repo-root ref:
|
||||
// the fileRef regex anchors on the `src`/`open-sse`/… token, so a leading `/`
|
||||
// means the real reference is `<something>/src/...` — either a relative example
|
||||
// path (`./src/index.ts`, a PII-pattern sample) or a workspace-package path
|
||||
// (`@omniroute/opencode-provider/src/index.ts`). Neither resolves from repo root.
|
||||
if (m.index > 0 && textNoCode[m.index - 1] === "/") continue;
|
||||
const ln = lineOf(text, m.index);
|
||||
findings.push({
|
||||
kind: "file-ref",
|
||||
@@ -601,12 +783,15 @@ function scanDocFile(absPath, index) {
|
||||
// ── Main ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export function runFabricatedDocsCheck(opts = {}) {
|
||||
const index = buildCodebaseIndex();
|
||||
const files = allScanFiles();
|
||||
// `root` lets tests run the full pipeline against a fixture tree (with its own
|
||||
// docs/, src/, .env.example) instead of the live repo.
|
||||
const root = opts.root ?? ROOT;
|
||||
const index = opts.index ?? buildCodebaseIndex(root);
|
||||
const files = allScanFiles(root);
|
||||
|
||||
const allFindings = [];
|
||||
for (const f of files) {
|
||||
const result = scanDocFile(f, index);
|
||||
const result = scanDocFile(f, index, root);
|
||||
if (result.findings.length > 0) {
|
||||
allFindings.push(result);
|
||||
}
|
||||
@@ -699,6 +884,10 @@ function main() {
|
||||
|
||||
console.log(formatHumanReport(result));
|
||||
console.log();
|
||||
if (totalFindings === 0) {
|
||||
// Clean run — pass regardless of mode.
|
||||
process.exit(0);
|
||||
}
|
||||
if (STRICT) {
|
||||
console.error(`✗ ${totalFindings} claim(s) drift from source. Failing (--strict).`);
|
||||
process.exit(1);
|
||||
|
||||
@@ -69,7 +69,7 @@ For authoritative coverage requirements and test execution guidelines, see [`CON
|
||||
### Adding a New Domain Module
|
||||
|
||||
1. Create `src/lib/db/[module].ts` with CRUD functions (create, read, update, delete, list)
|
||||
2. Export from `src/lib/db/localDb.ts` (add re-export)
|
||||
2. Export from `src/lib/localDb.ts` (add re-export)
|
||||
3. If new tables required: create migration in `db/migrations/NNN_[description].sql`
|
||||
4. Run migration via `migrationRunner.ts` at startup (automatic)
|
||||
5. Add unit tests in `tests/unit/db/[module].test.mjs`
|
||||
|
||||
@@ -9,6 +9,45 @@ import {
|
||||
formatHumanReport,
|
||||
} from "../../scripts/check/check-fabricated-docs.mjs";
|
||||
|
||||
// ── Fixture helpers ─────────────────────────────────────────────────────────
|
||||
// The hardened checker accepts a `root` so we can run the full pipeline against a
|
||||
// throwaway fixture repo (its own docs/, src/, .env.example) instead of the live
|
||||
// tree. Each fixture is a minimal repo: a code surface to index + a single doc.
|
||||
|
||||
type Fixture = { docs?: Record<string, string>; files?: Record<string, string> };
|
||||
|
||||
function makeFixtureRoot(fx: Fixture): string {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "fab-docs-"));
|
||||
const write = (rel: string, content: string) => {
|
||||
const abs = path.join(root, rel);
|
||||
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
||||
fs.writeFileSync(abs, content);
|
||||
};
|
||||
// Always provide an AGENTS.md so allScanFiles() has at least one scan target.
|
||||
write("AGENTS.md", fx.docs?.["AGENTS.md"] ?? "# Fixture\n");
|
||||
for (const [rel, content] of Object.entries(fx.docs ?? {})) {
|
||||
if (rel === "AGENTS.md") write(rel, content);
|
||||
else write(path.join("docs", rel), content);
|
||||
}
|
||||
for (const [rel, content] of Object.entries(fx.files ?? {})) write(rel, content);
|
||||
return root;
|
||||
}
|
||||
|
||||
/** Returns the set of distinct `${kind}::${value}` findings for a fixture. */
|
||||
function findingsFor(fx: Fixture): Set<string> {
|
||||
const root = makeFixtureRoot(fx);
|
||||
try {
|
||||
const result = runFabricatedDocsCheck({ root });
|
||||
const out = new Set<string>();
|
||||
for (const f of result.files) {
|
||||
for (const finding of f.findings) out.add(`${finding.kind}::${finding.value}`);
|
||||
}
|
||||
return out;
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
// We can't easily mock the buildCodebaseIndex() which walks src/app/api etc.
|
||||
// Instead, we test the report-formatting logic and the no-findings path on
|
||||
// the real repo, which acts as a smoke test that the script runs end-to-end.
|
||||
@@ -73,3 +112,203 @@ test("formatHumanReport: groups findings by kind", () => {
|
||||
assert.match(out, /FAKE_VAR_X/);
|
||||
assert.match(out, /onFake/);
|
||||
});
|
||||
|
||||
// ─── Hardening: precision fixes (QG v2 Fase 9 T9) ───────────────────────────
|
||||
//
|
||||
// Each test runs the FULL pipeline (index + scan) over a fixture repo so it
|
||||
// exercises the real heuristic, not a stubbed index.
|
||||
|
||||
test("env-var: an `export const` UPPER_SNAKE identifier in backticks is NOT flagged", () => {
|
||||
const found = findingsFor({
|
||||
files: {
|
||||
"src/server/authz/routeGuard.ts":
|
||||
"export const LOCAL_ONLY_API_PREFIXES: ReadonlyArray<string> = [];\n",
|
||||
},
|
||||
docs: {
|
||||
"guide.md": "We expose `LOCAL_ONLY_API_PREFIXES` to gate loopback routes.\n",
|
||||
},
|
||||
});
|
||||
assert.ok(
|
||||
!found.has("env-var::LOCAL_ONLY_API_PREFIXES"),
|
||||
"export const identifier must not be flagged as a fabricated env var"
|
||||
);
|
||||
});
|
||||
|
||||
test("env-var: an enum / object-literal member (HALF_OPEN) in backticks is NOT flagged", () => {
|
||||
const found = findingsFor({
|
||||
files: {
|
||||
"src/shared/utils/circuitBreaker.ts":
|
||||
'export const STATE = {\n CLOSED: "CLOSED",\n HALF_OPEN: "HALF_OPEN",\n} as const;\n',
|
||||
},
|
||||
docs: { "resilience.md": "After the reset window the breaker enters `HALF_OPEN`.\n" },
|
||||
});
|
||||
assert.ok(!found.has("env-var::HALF_OPEN"), "object-literal/enum key must not be flagged");
|
||||
});
|
||||
|
||||
test('env-var: a var read via bracket notation process.env["X"] is NOT flagged', () => {
|
||||
const found = findingsFor({
|
||||
files: {
|
||||
"src/lib/memory/vectorStore.ts": 'const k = Number(process.env["MEMORY_RRF_K"] ?? 60);\n',
|
||||
},
|
||||
docs: { "memory.md": "Tune ranking with `MEMORY_RRF_K`.\n" },
|
||||
});
|
||||
assert.ok(!found.has("env-var::MEMORY_RRF_K"), 'process.env["X"] read must be indexed');
|
||||
});
|
||||
|
||||
test('env-var: a var read via an env helper (envInt("X")) is NOT flagged', () => {
|
||||
const found = findingsFor({
|
||||
files: {
|
||||
"open-sse/config/constants.ts":
|
||||
'const t = envInt("OMNIROUTE_CIRCUIT_BREAKER_OAUTH_THRESHOLD", 8);\n',
|
||||
},
|
||||
docs: { "cfg.md": "Override with `OMNIROUTE_CIRCUIT_BREAKER_OAUTH_THRESHOLD`.\n" },
|
||||
});
|
||||
assert.ok(
|
||||
!found.has("env-var::OMNIROUTE_CIRCUIT_BREAKER_OAUTH_THRESHOLD"),
|
||||
'envInt("X", …) helper read must be indexed'
|
||||
);
|
||||
});
|
||||
|
||||
test("env-var: a var read ONLY in tests/ (RUN_CHAOS_INT) is NOT flagged", () => {
|
||||
const found = findingsFor({
|
||||
files: {
|
||||
"tests/integration/resilience-chaos.test.ts":
|
||||
'const RUN = process.env.RUN_CHAOS_INT === "1";\n',
|
||||
},
|
||||
docs: { "testing.md": "Set `RUN_CHAOS_INT` to enable the chaos suite.\n" },
|
||||
});
|
||||
assert.ok(
|
||||
!found.has("env-var::RUN_CHAOS_INT"),
|
||||
"env vars read only in tests/ must be indexed, not flagged"
|
||||
);
|
||||
});
|
||||
|
||||
test("env-var: a var present only in .env.example is NOT flagged", () => {
|
||||
const found = findingsFor({
|
||||
files: { ".env.example": "# Contract\nSOME_DOCUMENTED_CONTRACT_VAR=value\n" },
|
||||
docs: { "env.md": "Configure `SOME_DOCUMENTED_CONTRACT_VAR` in your environment.\n" },
|
||||
});
|
||||
assert.ok(
|
||||
!found.has("env-var::SOME_DOCUMENTED_CONTRACT_VAR"),
|
||||
".env.example is the env contract — its vars are documented, not fabricated"
|
||||
);
|
||||
});
|
||||
|
||||
test("api-path: a documented prefix with sub-routes (/api/cloud/) is NOT flagged", () => {
|
||||
const found = findingsFor({
|
||||
files: {
|
||||
"src/app/api/cloud/auth/route.ts": "export async function GET() {}\n",
|
||||
},
|
||||
docs: { "cloud.md": "All cloud endpoints live under `/api/cloud/`.\n" },
|
||||
});
|
||||
assert.ok(
|
||||
!found.has("api-path::/api/cloud/"),
|
||||
"a prefix that is an ancestor of a real route.ts must resolve"
|
||||
);
|
||||
});
|
||||
|
||||
test("api-path: a dynamic-segment prefix (/api/services/{name}/) is NOT flagged", () => {
|
||||
const found = findingsFor({
|
||||
files: {
|
||||
"src/app/api/services/[name]/status/route.ts": "export async function GET() {}\n",
|
||||
},
|
||||
docs: { "services.md": "Hit `/api/services/{name}/status` for health.\n" },
|
||||
});
|
||||
assert.ok(
|
||||
!found.has("api-path::/api/services/{name}/status"),
|
||||
"[name] dynamic segments must match the documented {name} convention"
|
||||
);
|
||||
});
|
||||
|
||||
test("hook: a real callback now in KNOWN_HOOKS (onChunk) is NOT flagged", () => {
|
||||
const found = findingsFor({
|
||||
docs: { "playground.md": "The stream invokes `onChunk` for each delta.\n" },
|
||||
});
|
||||
assert.ok(!found.has("hook::onChunk"), "onChunk is a real callback and must not be flagged");
|
||||
});
|
||||
|
||||
test("file-ref: a tutorial placeholder (src/app/api/your-route/route.ts) is NOT flagged", () => {
|
||||
const found = findingsFor({
|
||||
docs: {
|
||||
"scenario.md": "Create `src/app/api/your-route/route.ts` with GET/POST handlers.\n",
|
||||
},
|
||||
});
|
||||
assert.ok(
|
||||
!found.has("file-ref::src/app/api/your-route/route.ts"),
|
||||
"your-* / my* placeholders in how-to scenarios must not be flagged"
|
||||
);
|
||||
});
|
||||
|
||||
// ─── ANTI-OVER-SUPPRESSION GUARD (the most important test) ──────────────────
|
||||
//
|
||||
// The hardening eliminates false positives by adding PRECISION, never by blinding
|
||||
// the checker. A reference to something that genuinely does not exist MUST still be
|
||||
// flagged — otherwise the gate is worthless.
|
||||
|
||||
test("ANTI-OVER-SUPPRESSION: a reference to a genuinely missing file IS still flagged", () => {
|
||||
const found = findingsFor({
|
||||
docs: { "bad.md": "See the handler in `src/nao/existe.ts` for details.\n" },
|
||||
});
|
||||
assert.ok(
|
||||
found.has("file-ref::src/nao/existe.ts"),
|
||||
"a non-existent file reference must remain flagged — precision must not blind detection"
|
||||
);
|
||||
});
|
||||
|
||||
test("ANTI-OVER-SUPPRESSION: a truly fabricated env var IS still flagged", () => {
|
||||
const found = findingsFor({
|
||||
// No code reads it, not a code identifier, not in .env.example.
|
||||
docs: { "bad.md": "Set `TOTALLY_FABRICATED_ENV_VAR_XYZ` to enable nothing.\n" },
|
||||
});
|
||||
assert.ok(
|
||||
found.has("env-var::TOTALLY_FABRICATED_ENV_VAR_XYZ"),
|
||||
"a fabricated env var must remain flagged"
|
||||
);
|
||||
});
|
||||
|
||||
test("ANTI-OVER-SUPPRESSION: a fabricated API path with no backing route.ts IS still flagged", () => {
|
||||
const found = findingsFor({
|
||||
files: { "src/app/api/cloud/auth/route.ts": "export async function GET() {}\n" },
|
||||
// /api/imaginary/* has no route.ts anywhere and is not an ancestor of one.
|
||||
docs: { "bad.md": "Call `/api/imaginary/widget` to do magic.\n" },
|
||||
});
|
||||
assert.ok(
|
||||
found.has("api-path::/api/imaginary/widget"),
|
||||
"an API path with no backing route must remain flagged"
|
||||
);
|
||||
});
|
||||
|
||||
test("env-var: a doc explicitly stating a var does NOT exist is NOT flagged (documents absence)", () => {
|
||||
const found = findingsFor({
|
||||
docs: {
|
||||
"memory.md":
|
||||
"There are no env vars to tune weights (`MEMORY_RRF_VECTOR_WEIGHT` does not exist).\n",
|
||||
},
|
||||
});
|
||||
assert.ok(
|
||||
!found.has("env-var::MEMORY_RRF_VECTOR_WEIGHT"),
|
||||
"documenting a var's absence is not fabricating it"
|
||||
);
|
||||
});
|
||||
|
||||
test("env-var: a doc saying an override is 'not yet implemented' is NOT flagged", () => {
|
||||
const found = findingsFor({
|
||||
docs: {
|
||||
"zed.md": "A `ZED_CONFIG_PATH` environment variable override is not yet implemented.\n",
|
||||
},
|
||||
});
|
||||
assert.ok(!found.has("env-var::ZED_CONFIG_PATH"), "not-yet-implemented is an absence statement");
|
||||
});
|
||||
|
||||
test("ANTI-OVER-SUPPRESSION: a fabricated env var on a normal (non-negated) line IS still flagged", () => {
|
||||
// Same shape as the negation cases above, but WITHOUT an absence statement —
|
||||
// proves the negation skip is scoped to lines that disclaim the var, not a blanket
|
||||
// suppression of every memory/config-looking name.
|
||||
const found = findingsFor({
|
||||
docs: { "bad.md": "Set `MADE_UP_TUNING_KNOB_FOR_NOTHING` to change behavior.\n" },
|
||||
});
|
||||
assert.ok(
|
||||
found.has("env-var::MADE_UP_TUNING_KNOB_FOR_NOTHING"),
|
||||
"a fabricated env var on a plain line must still be flagged"
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user