Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
19d91d8
Release v3.8.34 (#4614)
diegosouzapw Jun 23, 2026
cadc3f1
Release v3.8.35 (#4743)
diegosouzapw Jun 23, 2026
8d50c9d
chore(release): open v3.8.36 development cycle
Jun 23, 2026
e494afe
refactor(chatCore): extrai resolveCompressionSettings (#3501) (#4826)
diegosouzapw Jun 23, 2026
e420c34
refactor(chatCore): extrai predicados puros de combo de compressão (#…
diegosouzapw Jun 23, 2026
34b510a
refactor(chatCore): extrai emitOutputStyleTelemetry (#3501) (#4811)
diegosouzapw Jun 23, 2026
812f2f2
refactor(chatCore): extrai writeCompressionAnalytics (bloco analytics…
diegosouzapw Jun 23, 2026
eb1920b
refactor(chatCore): extrai runPluginOnRequestHook (#3501) (#4827)
diegosouzapw Jun 23, 2026
e85fb9b
refactor(chatCore): extrai applyClientUsageBuffer (buffer/estimate de…
diegosouzapw Jun 23, 2026
5c0b29e
refactor(chatCore): extrai buildPostCallGuardrailContext (contexto gu…
diegosouzapw Jun 23, 2026
1244e49
refactor(chatCore): extrai storeSemanticCacheResponse (cache-store no…
diegosouzapw Jun 23, 2026
867e904
refactor(chatCore): extrai buildNonStreamingResponseHeaders (headers …
diegosouzapw Jun 23, 2026
c69bd6a
refactor(chatCore): extrai maybeConvertJsonBodyToSse (#3089 JSON→SSE …
diegosouzapw Jun 23, 2026
e87d35c
refactor(chatCore): extrai assembleStreamingResponseHeaders (headers …
diegosouzapw Jun 23, 2026
2cd5609
refactor(chatCore): extrai storeStreamingSemanticCacheResponse (cache…
diegosouzapw Jun 23, 2026
5bffefb
refactor(chatCore): extrai assembleStreamingPipeline (chain de transf…
diegosouzapw Jun 23, 2026
7f5b1e4
ci(quality): shift heavy validations to the PR→release fast-path (rel…
diegosouzapw Jun 23, 2026
c922b21
fix(quota): cota exclusiva lista qtSd/ no /v1/models (#4806) + limite…
diegosouzapw Jun 23, 2026
502f43c
feat(sse): add Google Flow video-generation provider (#4569) (#4769)
diegosouzapw Jun 23, 2026
fcf162d
fix(api): auth on compression run-telemetry + document OMNIROUTE_EVAL…
diegosouzapw Jun 23, 2026
fc2fdc7
fix(translator): strip top-level client_metadata on the OpenAI passth…
diegosouzapw Jun 24, 2026
ea406b2
fix(translator): normalize `developer` role to `system` for OpenAI-fo…
diegosouzapw Jun 24, 2026
2a90a9e
fix(translator): emit </think> close marker for Anthropic thinking bl…
diegosouzapw Jun 24, 2026
a9368af
fix(translator): normalize tools to Anthropic-native shape for non-An…
diegosouzapw Jun 24, 2026
35a3962
fix(gemini): preserve `pattern` in antigravity tool schema sanitizer …
diegosouzapw Jun 24, 2026
f4fa983
fix(perplexity): validate API keys via /v1/models endpoint (#4654)
diegosouzapw Jun 24, 2026
d2aa67a
fix(image): prevent compatible nodes from shadowing provider aliases …
diegosouzapw Jun 24, 2026
1ad6c46
fix(cli-tools): tolerate JSONC (comments, trailing commas) in tool se…
diegosouzapw Jun 24, 2026
983e152
fix(security): validate kiro region to prevent SSRF (GHSA-6mwv-4mrm-5…
diegosouzapw Jun 24, 2026
e176774
fix(cli): harden the systray2 tray runtime (port of 9router#1080) (#4…
diegosouzapw Jun 24, 2026
32b2df9
fix(test): validate anthropic-compatible connections via POST /v1/mes…
diegosouzapw Jun 24, 2026
607ad12
fix(executors): strip params unsupported by the target provider/model…
diegosouzapw Jun 24, 2026
be2ab9e
fix(claude-oauth): respect 429 backoff on usage endpoint to reduce sp…
diegosouzapw Jun 24, 2026
69703ee
feat(api/v1): include alias-backed models in /v1/models listing (#4630)
diegosouzapw Jun 24, 2026
306863d
chore(quality): rebaseline catalog.ts 1574->1577 (#4630 aliases sobre…
diegosouzapw Jun 24, 2026
ec1c5df
feat(compression): Kiro/CodeWhisperer tool-result compression engine …
diegosouzapw Jun 24, 2026
486f017
fix(security): don't trust loopback socket as local when behind rever…
diegosouzapw Jun 24, 2026
b92f045
fix(opencode): preserve DeepSeek reasoning content in streamed respon…
diegosouzapw Jun 24, 2026
57de29a
fix(copilot,antigravity): cap maxOutputTokens at 16384 to stop "Inval…
diegosouzapw Jun 24, 2026
c02aa7b
fix(dashboard): show custom vision models in LLM selector (#4653)
diegosouzapw Jun 24, 2026
6fe21e5
fix(claude): omit adaptive thinking + output_config.effort for haiku …
diegosouzapw Jun 24, 2026
2d09f72
feat(provider): CodeBuddy CN (copilot.tencent.com) — full stack (#4664)
diegosouzapw Jun 24, 2026
122f6d3
feat(combo): Fusion strategy — parallel panel + judge synthesis (16th…
diegosouzapw Jun 24, 2026
396a9a7
feat(proxy-pool): Deno Deploy relays + group action buttons (#4643)
diegosouzapw Jun 24, 2026
9244d93
fix(security): pin image fetch DNS resolution to prevent SSRF rebindi…
diegosouzapw Jun 24, 2026
cc8e1be
fix(github): route Copilot Codex models to /responses (port from 9rou…
diegosouzapw Jun 24, 2026
b599577
fix(copilot): never route Gemini/Claude variants to /responses (chat-…
diegosouzapw Jun 24, 2026
dad55fe
docs(ops): add canonical incident response runbook (#4868)
KooshaPari Jun 24, 2026
21aa561
docs(perf): add per-endpoint p50/p95/p99 latency + cost budgets (#4867)
KooshaPari Jun 24, 2026
9a5627a
fix(proxy): fan out direct dispatcher streams (#4803)
makcimbx Jun 24, 2026
805519b
fix(antigravity): exclude standard Gemini rate limit message from quo…
Chewji9875 Jun 24, 2026
e1ffc8e
fix(sse): skip third-party tool-name cloak for Anthropic server tools…
NomenAK Jun 24, 2026
604654c
fix(install): make transformers optional for CUDA-host installs (#4807)
megamen32 Jun 24, 2026
e99e288
fix(combo): propagate selected connection ID to fallback error respon…
Chewji9875 Jun 24, 2026
459ffcf
fix db storage tuning settings (#4834)
rdself Jun 24, 2026
108e93e
fix(sse): drop ccp pin when pinned provider is durably unhealthy (fai…
costaeder Jun 24, 2026
86b14ad
fix(claude): skip mcp__ tool-name cloak + guard missing connectionId …
costaeder Jun 24, 2026
4d61198
chore(quality): reconcile env-doc + file-size base-reds in release/v3…
diegosouzapw Jun 24, 2026
eb175db
fix(codex): drop non-standard codex.* events that break responses.str…
jeffer1312 Jun 24, 2026
2a7f2c8
feat(routing): honor X-Route-Model header to override body.model (#4863)
costaeder Jun 24, 2026
c522454
feat(live-ws): allow non-loopback clients via LIVE_WS_ALLOWED_HOSTS (…
KooshaPari Jun 24, 2026
2039095
chore(claude,codex): bump pinned CLI identity — Claude 2.1.158→2.1.18…
diegosouzapw Jun 24, 2026
5c0cda9
fix(security): SSRF allowlist bypass via x-relay-path nos relays Deno…
diegosouzapw Jun 24, 2026
27c994f
feat(quota): recuperação proativa de conexões em cooldown (cron heal)…
diegosouzapw Jun 24, 2026
4305ef8
fix(quota): policy inválida não vaza allow + guard connectionIds vazi…
diegosouzapw Jun 24, 2026
70afa2b
feat(quota): saturação real do Claude no fair-share via /api/oauth/us…
diegosouzapw Jun 24, 2026
f9ccf34
chore(dashboard): rename Qoder display label from "Qoder AI" to "Qode…
diegosouzapw Jun 24, 2026
9b07e0e
fix(ci): include coverage/lcov.info in coverage-report artifact for S…
diegosouzapw Jun 24, 2026
95e26a0
fix(cli): bump better-sqlite3 runtime pin to 12.10.1 for Node 26 (#4685)
diegosouzapw Jun 24, 2026
ec0572d
docs: clarify Kiro is ~50 credits/month per account, not unlimited (#…
diegosouzapw Jun 24, 2026
db192ce
docs(agentbridge): document Electron NODE_EXTRA_CA_CERTS, real model …
diegosouzapw Jun 24, 2026
413d1d3
docs(ops): document the release-green family (green-prs, check:releas…
diegosouzapw Jun 24, 2026
7a6f432
fix(translator): replay reasoning_content on plain Xiaomi MiMo turns …
diegosouzapw Jun 24, 2026
8251e7b
feat(opencode-go): advertise glm-5.2 and kimi-k2.7-code (align with o…
diegosouzapw Jun 24, 2026
1a1a51c
feat(db): track API endpoint dimension on usage_history (#4676)
diegosouzapw Jun 24, 2026
078785c
fix(cli): SIGKILL systray child PID before IPC close to avoid macOS N…
diegosouzapw Jun 24, 2026
8080800
feat(proxy-pool): Cloudflare Workers proxy deployer + pool integratio…
diegosouzapw Jun 24, 2026
82c21ab
fix(combo-api): gate composite-tiers validation behind graph-touching…
KooshaPari Jun 24, 2026
3bc0667
fix(combos): guard isActive toggle against partial-state 400
KooshaPari Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
116 changes: 95 additions & 21 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,22 @@ NEXT_PUBLIC_CLOUD_URL=
#OMNIROUTE_CODEWHISPERER_BASE_URL=https://codewhisperer.us-east-1.amazonaws.com
#OMNIROUTE_OPENCODE_QUOTA_URL=https://opencode.ai/zen/go/v1/quota
#OMNIROUTE_OPENCODE_GO_QUOTA_URL=https://api.z.ai/api/monitor/usage/quota/limit
#OMNIROUTE_OPENCODE_GO_DASHBOARD_URL=https://opencode.ai/workspace
#OMNIROUTE_OLLAMA_CLOUD_USAGE_URL=https://ollama.com/settings

# OpenCode Go dashboard quota scraping. Prefer configuring these per connection
# in Dashboard → Providers → OpenCode Go. Env vars are useful for headless
# deployments or shared server defaults. The cookie is sensitive.
#OPENCODE_GO_WORKSPACE_ID=wrk_...
#OMNIROUTE_OPENCODE_GO_WORKSPACE_ID=wrk_...
#OPENCODE_GO_AUTH_COOKIE=auth=...
#OMNIROUTE_OPENCODE_GO_AUTH_COOKIE=auth=...

# Ollama Cloud quota scraping. Prefer configuring this per connection in
# Dashboard → Providers → Ollama Cloud. The cookie is sensitive.
#OLLAMA_USAGE_COOKIE=__Secure-session=...
#OLLAMA_CLOUD_USAGE_COOKIE=__Secure-session=...
#OMNIROUTE_OLLAMA_USAGE_COOKIE=__Secure-session=...

# ═══════════════════════════════════════════════════════════════════════════════
# 8. OUTBOUND PROXY (Upstream Provider Calls)
Expand Down Expand Up @@ -535,6 +551,16 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500
# heuristic in instrumentation-node.ts. Default: unset (tests skip background).
#OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS=1

# Proactive connection-cooldown recovery (#8): re-validates connections whose
# transient `rate_limited_until` window has elapsed OUTSIDE the request hot path,
# so the first request after a cooldown does not pay the probe latency. Lazy
# recovery in getProviderCredentials still applies regardless. Used by:
# src/lib/quota/connectionRecovery.ts.
# Tick cadence (ms). Default 60000, floor 5000.
# OMNIROUTE_CONNECTION_RECOVERY_INTERVAL_MS=60000
# Disable the proactive recovery scheduler entirely (default: false).
# OMNIROUTE_DISABLE_CONNECTION_RECOVERY=false

# Background job interval for budget reset checks (ms). Default: 600000 (10m).
# Used by: src/lib/jobs/budgetResetJob.ts. Floor: 10000.
#OMNIROUTE_BUDGET_RESET_JOB_INTERVAL_MS=600000
Expand Down Expand Up @@ -582,6 +608,11 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500
# Used by: scripts/postinstall.mjs.
#OMNIROUTE_SKIP_POSTINSTALL=0

# Operator-supplied JSON credentials for the offline compression-eval CLI
# (parsed with JSON.parse; leave unset for a dry run). Developer tooling only.
# Used by: scripts/compression-eval/index.ts. Default: {} (empty).
#OMNIROUTE_EVAL_CREDENTIALS={}

# Skip the DB healthcheck entirely on startup (useful for short-lived tasks / tests).
# Used by: src/lib/db/core.ts, src/lib/db/healthCheck.ts. Set to 1 to disable. Default: 0.
#OMNIROUTE_SKIP_DB_HEALTHCHECK=0
Expand Down Expand Up @@ -776,7 +807,7 @@ GITHUB_OAUTH_CLIENT_ID=Iv1.b507a08c87ecfe98
# Used by: open-sse/executors/base.ts — buildHeaders() dynamic lookup.
# Update these when providers release new CLI versions to avoid blocks.

CLAUDE_USER_AGENT="claude-cli/2.1.158 (external, cli)"
CLAUDE_USER_AGENT="claude-cli/2.1.187 (external, cli)"

# Disable the deterministic tool-name cloak applied on both Anthropic-bound paths
# (executors/base.ts native OAuth + executors/cliproxyapi.ts CLIProxyAPI) —
Expand All @@ -785,7 +816,7 @@ CLAUDE_USER_AGENT="claude-cli/2.1.158 (external, cli)"
# stream with a misleading 400 out-of-extra-usage placeholder. Set to true to
# forward the original names verbatim (debugging only).
# CLAUDE_DISABLE_TOOL_NAME_CLOAK=false
CODEX_USER_AGENT="codex-cli/0.132.0 (Windows 10.0.26200; x64)"
CODEX_USER_AGENT="codex-cli/0.142.0 (Windows 10.0.26200; x64)"
GITHUB_USER_AGENT="GitHubCopilotChat/0.45.1"
ANTIGRAVITY_USER_AGENT="antigravity/2.0.1 linux/arm64 google-api-nodejs-client/10.3.0"
KIRO_USER_AGENT="AWS-SDK-JS/3.0.0 kiro-ide/1.0.0"
Expand All @@ -795,14 +826,25 @@ KIRO_USER_AGENT="AWS-SDK-JS/3.0.0 kiro-ide/1.0.0"
# rather than a secret. Only override if AWS ever starts enforcing this field.
# Used by: src/lib/oauth/constants/oauth.ts (KIRO_CONFIG.socialClientId).
# KIRO_OAUTH_CLIENT_ID=kiro-cli
# Enable full per-frame message CRC validation for Kiro streams. Off by default
# because it is O(frame bytes) on the main thread; use only for debugging
# suspected corrupted-stream issues.
# Used by: open-sse/executors/kiro.ts
# KIRO_VERIFY_FULL_CRC=false
QODER_USER_AGENT="Qoder-Cli"
QWEN_USER_AGENT="QwenCode/0.15.11 (linux; x64)"
CURSOR_USER_AGENT="Cursor/3.4"
GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"

# Override Codex client version sent in headers independently of the
# CODEX_USER_AGENT string. Used by: open-sse/config/codexClient.ts.
# CODEX_CLIENT_VERSION=0.132.0
# CODEX_CLIENT_VERSION=0.142.0

# Kill-switch to strip non-standard `codex.*` SSE events (e.g. codex.rate_limits)
# from the Codex Responses stream. These frames break the OpenAI SDK's
# responses.stream() with a 502 "Controller is already closed". Off by default;
# set to true/1/yes to enable. Used by: open-sse/executors/codex.ts.
# OMNIROUTE_CODEX_DROP_NONSTANDARD_EVENTS=true

# ═══════════════════════════════════════════════════════════════════════════════
# 13. CLI FINGERPRINT COMPATIBILITY (Anti-Detection)
Expand Down Expand Up @@ -929,6 +971,15 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
# OMNIROUTE_CIRCUIT_BREAKER_LOCAL_THRESHOLD=2
# OMNIROUTE_CIRCUIT_BREAKER_LOCAL_RESET_MS=15000

# ── Context-cache pin health gate ──
# Used by: open-sse/services/combo.ts. When a context-cache pin points at a
# provider that is durably unhealthy, the pin is dropped to allow failover.
# PIN_DROP_BACKOFF_LEVEL gates how deep a connection's backoff must be before the
# pin is considered durably unhealthy; PIN_DROP_GRACE_MS is the anti-flap window
# that tolerates brief transient cooldowns before dropping the pin.
# PIN_DROP_BACKOFF_LEVEL=2
# PIN_DROP_GRACE_MS=20000

# ── Stream idle detection ──
# STREAM_IDLE_TIMEOUT_MS=600000 # Max silence between SSE chunks (default: 600000)
# # Extended-thinking models rarely pause >90s.
Expand Down Expand Up @@ -1001,6 +1052,11 @@ APP_LOG_TO_FILE=true
# Default: 100000
# CALL_LOGS_TABLE_MAX_ROWS=100000

# Maximum age for orphaned active request log entries before the in-memory
# pending-request reaper removes them. Accepts milliseconds.
# Default: 3600000 (1 hour)
# MAX_PENDING_REQUEST_AGE_MS=3600000

# Whether call log pipeline capture stores stream chunks when enabled in settings.
# Only applies when call_log_pipeline_enabled=true.
# Default: true
Expand Down Expand Up @@ -1174,6 +1230,34 @@ APP_LOG_TO_FILE=true
# Used by: open-sse/executors/cloudflare-ai.ts
# CLOUDFLARE_ACCOUNT_ID=

# ── Deno Deploy proxy relay (#4643 / 9router#1437) ──
# Override the Deno Deploy REST API base used by the proxy-pool relay deployer.
# Default: https://api.deno.com/v2 (omit unless mocking).
# Used by: src/app/api/settings/proxy/deno-deploy/route.ts
# DENO_DEPLOY_API_BASE=https://api.deno.com/v2

# Default Deno Deploy app name suggested in the "Deploy Relay" modal.
# Used by: src/app/(dashboard)/dashboard/settings/components/proxy/DenoRelayModal.tsx
# NEXT_PUBLIC_DENO_RELAY_DEFAULT_PROJECT=omniroute-deno-relay

# Set to "false" to hide the Deno Deploy relay option from the Proxy Pool tab.
# Used by: src/app/(dashboard)/dashboard/settings/components/proxy/ProxyPoolTab.tsx
# NEXT_PUBLIC_DENO_RELAY_ENABLED=true

# ── Cloudflare Workers proxy relay (#4640 / 9router#1360) ──
# Override the Cloudflare REST API base used by the proxy-pool relay deployer.
# Default: https://api.cloudflare.com/client/v4 (omit unless mocking).
# Used by: src/app/api/settings/proxy/cloudflare-deploy/route.ts
# CLOUDFLARE_API_BASE=https://api.cloudflare.com/client/v4

# Default worker project name suggested in the "Deploy Relay" modal.
# Used by: src/app/(dashboard)/dashboard/settings/components/proxy/CloudflareRelayModal.tsx
# NEXT_PUBLIC_CLOUDFLARE_RELAY_DEFAULT_PROJECT=omniroute-relay

# Set to "false" to hide the Cloudflare Workers relay option from the Proxy Pool tab.
# Used by: src/app/(dashboard)/dashboard/settings/components/proxy/ProxyPoolTab.tsx
# NEXT_PUBLIC_CLOUDFLARE_RELAY_ENABLED=true

# ── Cloudflare Tunnel (cloudflared) ──
# Custom path to cloudflared binary for tunnel management.
# Used by: src/lib/cloudflaredTunnel.ts
Expand Down Expand Up @@ -1614,6 +1698,14 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
# QUOTA_CONSUMPTION_RETENTION_DAYS=14 # GC de buckets quota_consumption.updated_at antigos
# QUOTA_PREFLIGHT_CUTOFF_ENABLED=false # opt-in (default OFF): hard quota cutoff drops low-quota candidates before auto-routing scoring

# ─── Auto-Combo tier filter (#4517) ───────────────────────────────────────
# When an `auto/<category>:free` (or any `:<tier>`) request matches NO connected
# candidates, OmniRoute returns an EMPTY pool by default — so `:free` really means
# "free tier only" and a paid model is never picked just because no free provider is
# connected. Set this to `true`/`1` to restore the legacy behavior of falling back to
# the full (unfiltered) pool with a warning. Source: open-sse/services/autoCombo/virtualFactory.ts
# OMNIROUTE_AUTO_FREE_FALLBACK_TO_FULL_POOL=false

# ─── OpenCode config regeneration (scripts/ad-hoc/regen-opencode-config.ts) ───
# Base URL of the OmniRoute instance to query for /v1/models when regenerating
# an opencode.json with accurate limit.context values. Used by:
Expand Down Expand Up @@ -1703,21 +1795,3 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
# BIFROST_API_KEY=
# BIFROST_STREAMING_ENABLED=true
# BIFROST_TIMEOUT_MS=30000

# ── Scheduled VACUUM (Issue #4437) ────────────────────────────────────────
# Master switch for the scheduled SQLite VACUUM job.
# - OMNIROUTE_VACUUM_ENABLED=1 (default) → start a setInterval timer at boot
# that runs VACUUM once every OMNIROUTE_VACUUM_INTERVAL_HOURS.
# - 0 → disable the scheduler entirely (manual "Vacuum Now" still works).
# Source: src/lib/db/vacuumScheduler.ts
# OMNIROUTE_VACUUM_ENABLED=1

# How often to run VACUUM. Default: 24 hours. Minimum: 1. Must be an integer.
# The actual interval is computed lazily from OMNIROUTE_VACUUM_INTERVAL_HOURS
# at boot time, so changes here require a restart (or `omniroute vacuum restart`).
# OMNIROUTE_VACUUM_INTERVAL_HOURS=24

# Window in which the first VACUUM is allowed to run (cron-like start window).
# Default: 02:00-04:00 (local server time). Outside the window the scheduler
# waits until the window opens. Format: HH:MM-HH:MM (24-hour).
# OMNIROUTE_VACUUM_WINDOW=02:00-04:00
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ jobs:
runs-on: ubuntu-latest
steps:
# fetch-depth: 0 — the OpenAPI breaking-change gate (oasdiff) reads the base
# spec via `git show <base_ref>:docs/reference/openapi.yaml`; a shallow clone
# spec via `git show <base_ref>:docs/openapi.yaml`; a shallow clone
# would lack the base ref and the gate would self-skip (base-unresolved).
- uses: actions/checkout@v7
with:
Expand Down Expand Up @@ -318,7 +318,7 @@ jobs:
- name: Workflow lint (actionlint+zizmor, ratchet, blocking)
run: npm run check:workflows -- --ratchet
# OpenAPI breaking-change detection (oasdiff). Diffs the PR's public API
# contract (docs/reference/openapi.yaml) against the base branch's spec.
# contract (docs/openapi.yaml) against the base branch's spec.
# BLOCKING ratchet (Fase 9 Onda 0): reads metrics.openapiBreaking.value and
# exits 1 ONLY on a measured regression (count > baseline). It SKIPs (exit 0)
# when oasdiff is absent or the base spec can't be resolved — a missing
Expand Down Expand Up @@ -846,6 +846,7 @@ jobs:
path: |
coverage/coverage-summary.json
coverage/coverage-report.md
coverage/lcov.info
if-no-files-found: warn

sonarqube:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dast-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
- run: pip install schemathesis
- name: Schemathesis smoke (high-risk endpoints, blocking)
run: |
schemathesis run docs/reference/openapi.yaml --url http://localhost:20128 \
schemathesis run docs/openapi.yaml --url http://localhost:20128 \
--include-path-regex '^/v1/(chat/completions|models)$|^/api/(auth|keys)' \
--max-examples 8 --workers 4 --checks all --max-response-time 30 \
--request-timeout 20 --suppress-health-check all --no-color
Expand Down
137 changes: 137 additions & 0 deletions .github/workflows/nightly-release-green.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: Nightly Release-Green

# Solution D — continuous, NON-BLOCKING drift signal for the active release branch.
#
# WHY: the full gate (ci.yml) only runs on the release PR (PR → main), so reds
# accrue silently on release/** and explode — in layers — at release time. This
# nightly reproduces the release-equivalent validation on the active release branch
# HEAD and, when there are HARD failures, opens/updates a single tracking issue.
#
# It is NOT a required status check and never touches a contributor PR — it only
# reports. Ratchet drift (eslint warnings / cognitive-complexity / file-size) is
# expected mid-cycle and is reported but never raises the alarm on its own; only
# real defects (typecheck / lint errors / unit / vitest / db-rules / public-creds /
# package-artifact) flip the issue open.

on:
schedule:
- cron: "23 5 * * *" # 05:23 UTC daily — off-peak, distinct from other nightlies
workflow_dispatch:
inputs:
branch:
description: "Release branch to validate (default: highest release/vX.Y.Z)"
required: false
type: string

permissions:
contents: read
issues: write

concurrency:
group: nightly-release-green
cancel-in-progress: true

jobs:
release-green:
name: Validate active release branch
runs-on: ubuntu-latest
env:
JWT_SECRET: ci-nightly-secret-with-sufficient-length-for-validation
API_KEY_SECRET: ci-nightly-api-key-secret-long
DISABLE_SQLITE_AUTO_BACKUP: "true"
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false

- name: Resolve active release branch
id: branch
env:
INPUT_BRANCH: ${{ github.event.inputs.branch }}
run: |
set -euo pipefail
if [ -n "${INPUT_BRANCH:-}" ]; then
TARGET="$INPUT_BRANCH"
else
# highest release/vX.Y.Z by semver among remote branches
TARGET=$(git for-each-ref --format='%(refname:short)' 'refs/remotes/origin/release/v*' \
| sed 's#origin/##' \
| sort -t/ -k2 -V \
| tail -1)
fi
if [ -z "$TARGET" ]; then echo "No release/v* branch found"; exit 1; fi
# Strict format guard — reject anything that isn't release/vX.Y.Z (blocks
# ref/command injection via the workflow_dispatch input).
if ! printf '%s' "$TARGET" | grep -qE '^release/v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "Refusing non-canonical branch name: $TARGET"; exit 1
fi
echo "target=$TARGET" >> "$GITHUB_OUTPUT"
echo "Active release branch: $TARGET"

- name: Checkout the release branch
env:
TARGET: ${{ steps.branch.outputs.target }}
run: |
set -euo pipefail
git checkout "$TARGET"
git log -1 --oneline

- uses: actions/setup-node@v6
with:
node-version: "24"
cache: npm

- uses: ./.github/actions/npm-ci-retry

- name: Release-green validation (full)
id: validate
run: |
set +e
node scripts/quality/validate-release-green.mjs --json --with-build \
1> release-green.json 2> release-green.log
echo "exit=$?" >> "$GITHUB_OUTPUT"
echo "------- report -------"
cat release-green.log

- name: Open / update tracking issue on HARD failure
if: steps.validate.outputs.exit != '0'
env:
GH_TOKEN: ${{ github.token }}
TARGET: ${{ steps.branch.outputs.target }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
TITLE="🔴 Release branch not green: ${TARGET}"
{
echo "The nightly **release-green** validation found HARD failures on \`${TARGET}\`."
echo "These are real defects that would block the release PR — fix them in the"
echo "originating PR branch (via co-authorship), not by demanding it from contributors."
echo ""
echo "**Run:** ${RUN_URL}"
echo ""
echo '```'
sed -n '/──────── verdict ────────/,$p' release-green.log || tail -40 release-green.log
echo '```'
echo ""
echo "_Ratchet drift (eslint warnings / cognitive-complexity / file-size) listed above is expected mid-cycle and is rebaselined at release — it is NOT a contributor concern and did not, on its own, open this issue._"
} > issue-body.md

EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
--search "in:title $TITLE" --json number --jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$EXISTING" ]; then
gh issue comment "$EXISTING" --repo "$GITHUB_REPOSITORY" --body-file issue-body.md
echo "Updated existing issue #$EXISTING"
else
gh issue create --repo "$GITHUB_REPOSITORY" --title "$TITLE" --body-file issue-body.md
fi

- name: Upload report artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: release-green-report
path: |
release-green.json
release-green.log
if-no-files-found: ignore
2 changes: 1 addition & 1 deletion .github/workflows/nightly-schemathesis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
# PROVE the contract is fuzzable and surface regressions, not to gate the build.
continue-on-error: true
run: |
schemathesis run docs/reference/openapi.yaml \
schemathesis run docs/openapi.yaml \
--url http://localhost:20128 \
--max-examples 20 \
--workers 4 \
Expand Down
Loading